mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +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
|
|
@ -110,6 +110,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC
|
|||
#include "midi/juce_MidiFile.h"
|
||||
#include "midi/juce_MidiKeyboardState.h"
|
||||
#include "midi/juce_MidiRPN.h"
|
||||
#include "midi/juce_MidiDataConcatenator.h"
|
||||
#include "mpe/juce_MPEValue.h"
|
||||
#include "mpe/juce_MPENote.h"
|
||||
#include "mpe/juce_MPEZoneLayout.h"
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -59,44 +59,47 @@
|
|||
|
||||
#include "midi_io/juce_WaitFreeListeners.h"
|
||||
#include "midi_io/juce_WaitFreeListeners.cpp"
|
||||
#include "audio_io/juce_SampleRateHelpers.cpp"
|
||||
#include "midi_io/juce_MidiDeviceListConnectionBroadcaster.cpp"
|
||||
|
||||
#include "midi_io/ump/juce_UMPIOHelpers.cpp"
|
||||
#include "midi_io/ump/juce_UMPInput.cpp"
|
||||
#include "midi_io/ump/juce_UMPOutput.cpp"
|
||||
#include "midi_io/ump/juce_UMPLegacyVirtualInput.cpp"
|
||||
#include "midi_io/ump/juce_UMPLegacyVirtualOutput.cpp"
|
||||
#include "midi_io/ump/juce_UMPVirtualEndpoint.cpp"
|
||||
#include "midi_io/ump/juce_UMPSession.cpp"
|
||||
#include "midi_io/ump/juce_UMPEndpoints.cpp"
|
||||
|
||||
#include "audio_io/juce_SampleRateHelpers.cpp"
|
||||
#include "midi_io/juce_MidiDevices.cpp"
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
#include <juce_audio_basics/native/juce_CoreAudioTimeConversions_mac.h>
|
||||
#include <juce_audio_basics/native/juce_AudioWorkgroup_mac.h>
|
||||
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
|
||||
#include <juce_audio_basics/midi/ump/juce_UMP.h>
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC
|
||||
#define Point CarbonDummyPointName
|
||||
#define Component CarbonDummyCompName
|
||||
#import <CoreAudio/AudioHardware.h>
|
||||
#import <CoreMIDI/MIDIServices.h>
|
||||
#import <CoreMIDI/CoreMIDI.h>
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
#undef Point
|
||||
#undef Component
|
||||
|
||||
#include "native/juce_CoreAudio_mac.cpp"
|
||||
#include "native/juce_CoreMidi_mac.mm"
|
||||
|
||||
#elif JUCE_IOS
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreMIDI/MIDIServices.h>
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
#import <CoreMIDI/MIDINetworkSession.h>
|
||||
#endif
|
||||
#import <CoreMIDI/CoreMIDI.h>
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_graphics
|
||||
#include <juce_graphics/native/juce_CoreGraphicsHelpers_mac.h>
|
||||
#endif
|
||||
|
||||
#include "native/juce_Audio_ios.cpp"
|
||||
#include "native/juce_CoreMidi_mac.mm"
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_WINDOWS
|
||||
|
|
@ -132,9 +135,6 @@
|
|||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
#endif
|
||||
|
||||
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
|
||||
#include "native/juce_Midi_windows.cpp"
|
||||
|
||||
#if JUCE_ASIO
|
||||
/* This is very frustrating - we only need to use a handful of definitions from
|
||||
a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy
|
||||
|
|
@ -173,6 +173,7 @@
|
|||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-length-array")
|
||||
#include <alsa/asoundlib.h>
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
#include "native/juce_ALSA_weak_linux.h"
|
||||
#include "native/juce_ALSA_linux.cpp"
|
||||
#endif
|
||||
|
||||
|
|
@ -191,22 +192,19 @@
|
|||
|
||||
#undef SIZEOF
|
||||
|
||||
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
|
||||
#include "native/juce_Midi_linux.cpp"
|
||||
|
||||
//==============================================================================
|
||||
#elif JUCE_ANDROID
|
||||
|
||||
// Currently we're just using this for enum values
|
||||
#include <amidi/AMidi.h>
|
||||
|
||||
namespace juce
|
||||
{
|
||||
using RealtimeThreadFactory = pthread_t (*) (void* (*) (void*), void*);
|
||||
RealtimeThreadFactory getAndroidRealtimeThreadFactory();
|
||||
} // namespace juce
|
||||
|
||||
#include "native/juce_Audio_android.cpp"
|
||||
|
||||
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
|
||||
#include "native/juce_Midi_android.cpp"
|
||||
#include "native/juce_Audio_android.cpp"
|
||||
|
||||
#if JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE
|
||||
#include "native/juce_HighPerformanceAudioHelpers_android.h"
|
||||
|
|
@ -264,8 +262,6 @@ namespace juce
|
|||
#include "native/juce_JackAudio.cpp"
|
||||
#endif
|
||||
|
||||
#include "midi_io/juce_MidiDevices.cpp"
|
||||
|
||||
#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED
|
||||
namespace juce
|
||||
{
|
||||
|
|
@ -283,3 +279,25 @@ namespace juce
|
|||
#include "midi_io/juce_MidiMessageCollector.cpp"
|
||||
#include "sources/juce_AudioSourcePlayer.cpp"
|
||||
#include "sources/juce_AudioTransportSource.cpp"
|
||||
|
||||
#if JUCE_LINUX || JUCE_BSD
|
||||
#include "native/juce_Midi_linux.cpp"
|
||||
#elif JUCE_ANDROID
|
||||
#include "native/juce_Midi_android.cpp"
|
||||
#elif JUCE_MAC || JUCE_IOS
|
||||
#include "native/juce_CoreMidi_mac.mm"
|
||||
#elif JUCE_WINDOWS
|
||||
#if JUCE_USE_WINDOWS_MIDI_SERVICES
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4265)
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Devices.Enumeration.h>
|
||||
|
||||
#include <winrt/Microsoft.Windows.Devices.Midi2.h>
|
||||
#include <winrt/Microsoft.Windows.Devices.Midi2.Endpoints.Virtual.h>
|
||||
#include <winmidi/init/Microsoft.Windows.Devices.Midi2.Initialization.hpp>
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
#endif
|
||||
|
||||
#include "native/juce_Midi_windows.cpp"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -87,6 +87,40 @@
|
|||
#define JUCE_USE_WINRT_MIDI 0
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_USE_WINDOWS_MIDI_SERVICES
|
||||
Enables the use of Windows MIDI Services, in turn enabling MIDI 2.0 support on
|
||||
some Windows versions. At time of writing, this only works on the Canary insiders
|
||||
build of Windows 11, but eventually this will work on the final release of
|
||||
Windows 10 too.
|
||||
|
||||
In order to enable this functionality, your target and minimum Windows SDK
|
||||
versions must be set to at least 10.0.20348.0.
|
||||
|
||||
This functionality depends on the CppWinRT tool and the Windows MIDI Services
|
||||
NuGet package at build-time. If you're using the Projucer, this will be set up
|
||||
for you automatically when this flag is enabled. If you're using CMake, you'll
|
||||
need to pass "NEEDS_WINDOWS_MIDI_SERVICES TRUE" to your juce_add_<target> call.
|
||||
|
||||
Currently, the Windows MIDI Services NuGet package is not hosted in the official
|
||||
repository. In order to allow NuGet and/or Visual Studio to find the package,
|
||||
you'll need to set up a local repository pointing at a local folder holding the
|
||||
package. You can do this from the Visual Studio package management UI.
|
||||
|
||||
Because this API is implemented using code generated by the CppWinRT tool, it
|
||||
can only be built by compilers that understand the generated code. MSVC is
|
||||
most likely to work; some versions of Clang may work but this is not guaranteed.
|
||||
|
||||
Even once the Windows MIDI Services are stable and enabled in Windows,
|
||||
the service won't be usable until the service's SDK package is installed.
|
||||
Therefore, you will need to ensure that the installer for your software also
|
||||
installs this SDK package, or informs the user how to install the dependency.
|
||||
If the SDK is not available, JUCE will fall back to using the older WinRT
|
||||
API (if enabled) or the Win32 API otherwise. These APIs do not support MIDI 2.0.
|
||||
*/
|
||||
#ifndef JUCE_USE_WINDOWS_MIDI_SERVICES
|
||||
#define JUCE_USE_WINDOWS_MIDI_SERVICES 0
|
||||
#endif
|
||||
|
||||
/** Config: JUCE_ASIO
|
||||
Enables ASIO audio devices (MS Windows only).
|
||||
Turning this on means that you'll need to have the Steinberg ASIO SDK installed
|
||||
|
|
@ -164,6 +198,18 @@
|
|||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#include "midi_io/juce_ScheduledEventThread.h"
|
||||
#include "midi_io/ump/juce_UMPEndpointId.h"
|
||||
#include "midi_io/ump/juce_UMPBlock.h"
|
||||
#include "midi_io/ump/juce_UMPEndpoint.h"
|
||||
#include "midi_io/ump/juce_UMPDisconnectionListener.h"
|
||||
#include "midi_io/ump/juce_UMPInput.h"
|
||||
#include "midi_io/ump/juce_UMPOutput.h"
|
||||
#include "midi_io/ump/juce_UMPLegacyVirtualInput.h"
|
||||
#include "midi_io/ump/juce_UMPLegacyVirtualOutput.h"
|
||||
#include "midi_io/ump/juce_UMPVirtualEndpoint.h"
|
||||
#include "midi_io/ump/juce_UMPSession.h"
|
||||
#include "midi_io/ump/juce_UMPEndpoints.h"
|
||||
#include "midi_io/juce_MidiDevices.h"
|
||||
#include "midi_io/juce_MidiMessageCollector.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -35,11 +35,185 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
class MidiDeviceListConnectionBroadcaster final : private AsyncUpdater
|
||||
/* Identifies a single group of an endpoint. */
|
||||
struct EndpointGroup
|
||||
{
|
||||
ump::EndpointId endpointId;
|
||||
uint8_t group; // 0 to 15 inclusive
|
||||
|
||||
bool operator== (const EndpointGroup& other) const
|
||||
{
|
||||
const auto tie = [] (auto& x) { return std::tuple (x.endpointId, x.group); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const EndpointGroup& other) const { return ! operator== (other); }
|
||||
};
|
||||
|
||||
class MidiDeviceListConnectionBroadcaster : private AsyncUpdater,
|
||||
private ump::EndpointsListener
|
||||
{
|
||||
struct GroupId
|
||||
{
|
||||
String identifier;
|
||||
ump::IOKind kind;
|
||||
|
||||
auto tie() const { return std::tuple (identifier, kind); }
|
||||
bool operator== (const GroupId& other) const { return tie() == other.tie(); }
|
||||
bool operator!= (const GroupId& other) const { return tie() != other.tie(); }
|
||||
bool operator< (const GroupId& other) const { return tie() < other.tie(); }
|
||||
};
|
||||
|
||||
class Endpoints
|
||||
{
|
||||
auto tie() const { return std::tie (idBuffer, map, inputs, outputs); }
|
||||
|
||||
public:
|
||||
void refresh()
|
||||
{
|
||||
updateIdBuffer();
|
||||
map = computeMap();
|
||||
inputs = computeInfo (ump::IOKind::src);
|
||||
outputs = computeInfo (ump::IOKind::dst);
|
||||
}
|
||||
|
||||
std::optional<EndpointGroup> getEndpointGroupForId (ump::IOKind direction, const String& id) const
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
const auto iter = map.find ({ id, direction });
|
||||
|
||||
if (iter == map.end())
|
||||
return {};
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void getAllMidiDeviceInfo (ump::IOKind direction, Array<MidiDeviceInfo>& buffer) const
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
buffer.addArray (direction == ump::IOKind::src ? inputs : outputs);
|
||||
}
|
||||
|
||||
bool operator== (const Endpoints& other) const
|
||||
{
|
||||
return tie() == other.tie();
|
||||
}
|
||||
|
||||
bool operator!= (const Endpoints& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
private:
|
||||
void updateIdBuffer()
|
||||
{
|
||||
idBuffer.clear();
|
||||
|
||||
auto* ep = ump::Endpoints::getInstance();
|
||||
|
||||
if (ep == nullptr)
|
||||
return;
|
||||
|
||||
ep->getEndpoints (idBuffer);
|
||||
}
|
||||
|
||||
std::map<GroupId, EndpointGroup> computeMap() const
|
||||
{
|
||||
auto* ep = ump::Endpoints::getInstance();
|
||||
|
||||
if (ep == nullptr)
|
||||
return {};
|
||||
|
||||
std::map<GroupId, EndpointGroup> result;
|
||||
|
||||
for (const auto& id : idBuffer)
|
||||
{
|
||||
const auto info = ep->getStaticDeviceInfo (id);
|
||||
|
||||
if (! info.has_value())
|
||||
continue;
|
||||
|
||||
for (const auto kind : ump::ioKinds)
|
||||
{
|
||||
const auto groups = info->getLegacyIdentifiers (kind);
|
||||
|
||||
for (const auto [index, groupId] : enumerate (groups, uint8_t{}))
|
||||
{
|
||||
if (groupId.isNotEmpty())
|
||||
result.emplace (GroupId { groupId, kind }, EndpointGroup { id, index });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Array<MidiDeviceInfo> computeInfo (ump::IOKind dir) const
|
||||
{
|
||||
auto* ep = ump::Endpoints::getInstance();
|
||||
|
||||
if (ep == nullptr)
|
||||
return {};
|
||||
|
||||
Array<MidiDeviceInfo> result;
|
||||
|
||||
for (const auto& id : idBuffer)
|
||||
{
|
||||
const auto endpoint = ep->getEndpoint (id);
|
||||
|
||||
if (! endpoint.has_value())
|
||||
continue;
|
||||
|
||||
const auto info = ep->getStaticDeviceInfo (id);
|
||||
|
||||
if (! info.has_value())
|
||||
continue;
|
||||
|
||||
const auto groups = info->getLegacyIdentifiers (dir);
|
||||
|
||||
for (const auto [groupIndex, groupId] : enumerate (groups, uint8_t{}))
|
||||
{
|
||||
if (groupId.isEmpty())
|
||||
continue;
|
||||
|
||||
const auto blockName = findNameForGroup (groupIndex, dir, endpoint->getBlocks());
|
||||
|
||||
if (! blockName.has_value())
|
||||
continue;
|
||||
|
||||
const auto separator = endpoint->getName().isEmpty() || blockName->isEmpty() ? "" : " : ";
|
||||
const auto name = endpoint->getName() + separator + *blockName;
|
||||
|
||||
result.add (MidiDeviceInfo { name, groupId });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ump::EndpointId> idBuffer = std::invoke ([]() -> std::vector<ump::EndpointId>
|
||||
{
|
||||
auto* ep = ump::Endpoints::getInstance();
|
||||
|
||||
if (ep == nullptr)
|
||||
return {};
|
||||
|
||||
std::vector<ump::EndpointId> result;
|
||||
ep->getEndpoints (result);
|
||||
return result;
|
||||
});
|
||||
|
||||
std::map<GroupId, EndpointGroup> map = computeMap();
|
||||
Array<MidiDeviceInfo> inputs = computeInfo (ump::IOKind::src), outputs = computeInfo (ump::IOKind::dst);
|
||||
};
|
||||
|
||||
public:
|
||||
~MidiDeviceListConnectionBroadcaster() override
|
||||
{
|
||||
if (auto* instance = ump::Endpoints::getInstanceWithoutCreating())
|
||||
instance->removeListener (*this);
|
||||
|
||||
cancelPendingUpdate();
|
||||
}
|
||||
|
||||
|
|
@ -49,13 +223,66 @@ public:
|
|||
return callbacks.emplace (key++, std::move (callback)).first->first;
|
||||
}
|
||||
|
||||
void getAllMidiDeviceInfo (ump::IOKind direction, Array<MidiDeviceInfo>& buffer)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
if (! endpoints.has_value())
|
||||
endpoints = Endpoints{};
|
||||
|
||||
endpoints->getAllMidiDeviceInfo (direction, buffer);
|
||||
}
|
||||
|
||||
std::optional<MidiDeviceInfo> getInfoForId (ump::IOKind direction, const String& id)
|
||||
{
|
||||
if (const auto eg = getEndpointGroupForId (direction, id))
|
||||
return findInfoForDeviceIdentifier ({ id, direction }, *eg);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<EndpointGroup> getEndpointGroupForId (ump::IOKind direction, const String& id)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
if (! endpoints.has_value())
|
||||
endpoints = Endpoints{};
|
||||
|
||||
return endpoints->getEndpointGroupForId (direction, id);
|
||||
}
|
||||
|
||||
void remove (const MidiDeviceListConnection::Key k)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
callbacks.erase (k);
|
||||
}
|
||||
|
||||
void notify()
|
||||
static auto& get()
|
||||
{
|
||||
static MidiDeviceListConnectionBroadcaster result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
MidiDeviceListConnectionBroadcaster()
|
||||
{
|
||||
ump::Endpoints::getInstance()->addListener (*this);
|
||||
}
|
||||
|
||||
static std::optional<String> findNameForGroup (uint8_t group, ump::IOKind kind, Span<const ump::Block> blocks)
|
||||
{
|
||||
const auto matchingGroup = std::find_if (blocks.begin(), blocks.end(), [&] (const ump::Block& b)
|
||||
{
|
||||
return directionsMatch (b, kind) && group == b.getFirstGroup() && b.getNumGroups() == 1;
|
||||
});
|
||||
|
||||
if (matchingGroup != blocks.end())
|
||||
return matchingGroup->getName();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void endpointsChanged() override
|
||||
{
|
||||
auto* mm = MessageManager::getInstanceWithoutCreating();
|
||||
|
||||
|
|
@ -70,47 +297,60 @@ public:
|
|||
|
||||
cancelPendingUpdate();
|
||||
|
||||
if (auto prev = std::exchange (lastNotifiedState, State{}); prev != lastNotifiedState)
|
||||
if (auto prev = std::exchange (endpoints, Endpoints{}); prev != endpoints)
|
||||
for (auto it = callbacks.begin(); it != callbacks.end();)
|
||||
NullCheckedInvocation::invoke ((it++)->second);
|
||||
}
|
||||
|
||||
static auto& get()
|
||||
{
|
||||
static MidiDeviceListConnectionBroadcaster result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
MidiDeviceListConnectionBroadcaster() = default;
|
||||
|
||||
class State
|
||||
{
|
||||
Array<MidiDeviceInfo> ins = MidiInput::getAvailableDevices(),
|
||||
outs = MidiOutput::getAvailableDevices();
|
||||
auto tie() const
|
||||
{
|
||||
return std::tie (ins, outs);
|
||||
}
|
||||
|
||||
public:
|
||||
bool operator== (const State& other) const
|
||||
{
|
||||
return tie() == other.tie();
|
||||
}
|
||||
bool operator!= (const State& other) const
|
||||
{
|
||||
return tie() != other.tie();
|
||||
}
|
||||
};
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
notify();
|
||||
endpointsChanged();
|
||||
}
|
||||
|
||||
static bool directionsMatch (ump::BlockDirection d, ump::IOKind k)
|
||||
{
|
||||
switch (d)
|
||||
{
|
||||
case ump::BlockDirection::unknown: return true;
|
||||
case ump::BlockDirection::bidirectional: return true;
|
||||
case ump::BlockDirection::sender: return k == ump::IOKind::src;
|
||||
case ump::BlockDirection::receiver: return k == ump::IOKind::dst;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool directionsMatch (const ump::Block& b, ump::IOKind k)
|
||||
{
|
||||
return directionsMatch (b.getDirection(), k);
|
||||
}
|
||||
|
||||
static std::optional<MidiDeviceInfo> findInfoForDeviceIdentifier (const GroupId& key, const EndpointGroup& value)
|
||||
{
|
||||
auto* endpoints = ump::Endpoints::getInstance();
|
||||
|
||||
if (endpoints == nullptr)
|
||||
return {};
|
||||
|
||||
const auto endpoint = endpoints->getEndpoint (value.endpointId);
|
||||
|
||||
if (! endpoint.has_value())
|
||||
return {};
|
||||
|
||||
const auto blockName = findNameForGroup (value.group, key.kind, endpoint->getBlocks());
|
||||
|
||||
if (! blockName.has_value())
|
||||
return {};
|
||||
|
||||
const auto separator = endpoint->getName().isEmpty() || blockName->isEmpty() ? "" : " : ";
|
||||
const auto name = endpoint->getName() + separator + *blockName;
|
||||
|
||||
return MidiDeviceInfo { name, key.identifier };
|
||||
}
|
||||
|
||||
std::optional<Endpoints> endpoints;
|
||||
std::map<MidiDeviceListConnection::Key, std::function<void()>> callbacks;
|
||||
std::optional<State> lastNotifiedState;
|
||||
MidiDeviceListConnection::Key key = 0;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -35,144 +35,368 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
MidiDeviceListConnection::~MidiDeviceListConnection() noexcept
|
||||
MidiDeviceListConnection MidiDeviceListConnection::make (std::function<void()> callback)
|
||||
{
|
||||
if (broadcaster != nullptr)
|
||||
broadcaster->remove (key);
|
||||
auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
|
||||
|
||||
const auto key = broadcaster.add (std::move (callback));
|
||||
|
||||
MidiDeviceListConnection result;
|
||||
result.token = ErasedScopeGuard { [&broadcaster, key] { broadcaster.remove (key); } };
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MidiInputCallback::handlePartialSysexMessage ([[maybe_unused]] MidiInput* source,
|
||||
[[maybe_unused]] const uint8* messageData,
|
||||
[[maybe_unused]] int numBytesSoFar,
|
||||
[[maybe_unused]] double timestamp) {}
|
||||
static std::shared_ptr<ump::Session> getLegacySession()
|
||||
{
|
||||
static std::weak_ptr<ump::Session> weak;
|
||||
|
||||
if (auto strong = weak.lock())
|
||||
return strong;
|
||||
|
||||
if (auto session = ump::Endpoints::getInstance()->makeSession (ump::Endpoints::Impl::getGlobalMidiClientName()))
|
||||
{
|
||||
auto strong = std::make_shared<ump::Session> (std::move (session));
|
||||
weak = strong;
|
||||
return strong;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class MidiInput::Impl : private ump::Consumer
|
||||
{
|
||||
public:
|
||||
void start()
|
||||
{
|
||||
const SpinLock::ScopedLockType lock { spinLock };
|
||||
active = true;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
const SpinLock::ScopedLockType lock { spinLock };
|
||||
active = false;
|
||||
}
|
||||
|
||||
MidiDeviceInfo getDeviceInfo() const noexcept
|
||||
{
|
||||
return customName.has_value() ? storedInfo.withName (*customName) : storedInfo;
|
||||
}
|
||||
|
||||
void setName (String x)
|
||||
{
|
||||
customName = std::move (x);
|
||||
}
|
||||
|
||||
void addCallback (MidiInputCallback& cb)
|
||||
{
|
||||
callbacks.add (cb);
|
||||
}
|
||||
|
||||
void removeCallback (MidiInputCallback& cb)
|
||||
{
|
||||
callbacks.remove (cb);
|
||||
}
|
||||
|
||||
/* session may be null, in which case it's up to the caller to ensure that the session lives
|
||||
long enough for the connection to be useful.
|
||||
*/
|
||||
static std::unique_ptr<MidiInput> make (std::shared_ptr<ump::Session> session,
|
||||
ump::Input connection,
|
||||
uint8_t group,
|
||||
const MidiDeviceInfo& info,
|
||||
MidiInputCallback* cb,
|
||||
ump::LegacyVirtualInput virtualEndpoint)
|
||||
{
|
||||
auto result = rawToUniquePtr (new MidiInput);
|
||||
result->pimpl = rawToUniquePtr (new Impl (session,
|
||||
std::move (connection),
|
||||
group,
|
||||
result.get(),
|
||||
info,
|
||||
std::move (virtualEndpoint)));
|
||||
|
||||
if (cb != nullptr)
|
||||
result->addCallback (*cb);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t getGroup() const
|
||||
{
|
||||
return group;
|
||||
}
|
||||
|
||||
ump::EndpointId getEndpointId() const
|
||||
{
|
||||
return connection.getEndpointId();
|
||||
}
|
||||
|
||||
~Impl() override
|
||||
{
|
||||
connection.removeConsumer (*this);
|
||||
}
|
||||
|
||||
private:
|
||||
Impl (std::shared_ptr<ump::Session> s,
|
||||
ump::Input x,
|
||||
uint8_t g,
|
||||
MidiInput* o,
|
||||
MidiDeviceInfo i,
|
||||
ump::LegacyVirtualInput v)
|
||||
: session (s),
|
||||
virtualEndpoint (std::move (v)),
|
||||
connection (std::move (x)),
|
||||
storedInfo (i),
|
||||
group (g),
|
||||
owner (o)
|
||||
{
|
||||
connection.addConsumer (*this);
|
||||
}
|
||||
|
||||
void consume (ump::Iterator b, ump::Iterator e, double time) override
|
||||
{
|
||||
const SpinLock::ScopedTryLockType lock { spinLock };
|
||||
|
||||
if (! lock.isLocked() || ! active)
|
||||
return;
|
||||
|
||||
for (const auto& view : makeRange (b, e))
|
||||
{
|
||||
if (ump::Utils::getGroup (view[0]) != group)
|
||||
continue;
|
||||
|
||||
converter.convert (view, time, [this] (ump::BytesOnGroup v, double t)
|
||||
{
|
||||
const MidiMessage msg { v.bytes.data(), (int) v.bytes.size(), t };
|
||||
|
||||
callbacks.call ([&] (MidiInputCallback& l)
|
||||
{
|
||||
l.handleIncomingMidiMessage (owner, msg);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ump::Session> session;
|
||||
ump::LegacyVirtualInput virtualEndpoint;
|
||||
std::optional<String> customName;
|
||||
ump::Input connection;
|
||||
MidiDeviceInfo storedInfo;
|
||||
ump::ToBytestreamConverter converter { 4096 };
|
||||
WaitFreeListeners<MidiInputCallback> callbacks;
|
||||
uint8_t group{};
|
||||
MidiInput* owner = nullptr;
|
||||
SpinLock spinLock;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
MidiInput::MidiInput() = default;
|
||||
MidiInput::~MidiInput() = default;
|
||||
|
||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
|
||||
{
|
||||
Array<MidiDeviceInfo> result;
|
||||
MidiDeviceListConnectionBroadcaster::get().getAllMidiDeviceInfo (ump::IOKind::src, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
MidiDeviceInfo MidiInput::getDefaultDevice()
|
||||
{
|
||||
return getAvailableDevices().getFirst();
|
||||
}
|
||||
|
||||
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
|
||||
{
|
||||
const auto address = MidiDeviceListConnectionBroadcaster::get().getEndpointGroupForId (ump::IOKind::src, deviceIdentifier);
|
||||
|
||||
if (! address.has_value())
|
||||
return {};
|
||||
|
||||
const auto info = MidiDeviceListConnectionBroadcaster::get().getInfoForId (ump::IOKind::src, deviceIdentifier);
|
||||
|
||||
if (! info.has_value())
|
||||
return {};
|
||||
|
||||
auto session = getLegacySession();
|
||||
|
||||
if (session == nullptr)
|
||||
return {};
|
||||
|
||||
auto connection = session->connectInput (address->endpointId, ump::PacketProtocol::MIDI_1_0);
|
||||
|
||||
if (! connection.isAlive())
|
||||
return {};
|
||||
|
||||
return Impl::make (session, std::move (connection), address->group, *info, callback, {});
|
||||
}
|
||||
|
||||
static inline bool isValidMidi1VirtualEndpoint (const std::optional<ump::Endpoint>& ep,
|
||||
ump::BlockDirection dir)
|
||||
{
|
||||
if (! ep.has_value())
|
||||
return false;
|
||||
|
||||
if (! ep->hasMidi1Support())
|
||||
return false;
|
||||
|
||||
if (ep->getProtocol() != ump::PacketProtocol::MIDI_1_0)
|
||||
return false;
|
||||
|
||||
if (! ep->hasStaticBlocks())
|
||||
return false;
|
||||
|
||||
auto blocks = ep->getBlocks();
|
||||
const auto iter = std::find_if (blocks.begin(), blocks.end(), [&] (const ump::Block& b)
|
||||
{
|
||||
return b.getDirection() == dir
|
||||
&& b.getNumGroups() == 1
|
||||
&& b.isEnabled()
|
||||
&& b.getMIDI1ProxyKind() != ump::BlockMIDI1ProxyKind::inapplicable;
|
||||
});
|
||||
|
||||
if (iter == blocks.end())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& name, MidiInputCallback* callback)
|
||||
{
|
||||
auto session = getLegacySession();
|
||||
|
||||
if (! session)
|
||||
return {};
|
||||
|
||||
auto port = session->createLegacyVirtualInput (name);
|
||||
|
||||
if (! port)
|
||||
return {};
|
||||
|
||||
jassert (isValidMidi1VirtualEndpoint (ump::Endpoints::getInstance()->getEndpoint (port.getId()),
|
||||
ump::BlockDirection::receiver));
|
||||
|
||||
auto connection = session->connectInput (port.getId(), ump::PacketProtocol::MIDI_1_0);
|
||||
|
||||
if (! connection)
|
||||
return {};
|
||||
|
||||
return Impl::make (session, std::move (connection), 0, { name, {} }, callback, std::move (port));
|
||||
}
|
||||
|
||||
void MidiInput::start()
|
||||
{
|
||||
pimpl->start();
|
||||
}
|
||||
|
||||
void MidiInput::stop()
|
||||
{
|
||||
pimpl->stop();
|
||||
}
|
||||
|
||||
MidiDeviceInfo MidiInput::getDeviceInfo() const noexcept
|
||||
{
|
||||
return pimpl->getDeviceInfo();
|
||||
}
|
||||
|
||||
void MidiInput::setName (const String& newName) noexcept
|
||||
{
|
||||
pimpl->setName (newName);
|
||||
}
|
||||
|
||||
uint8_t MidiInput::getGroup() const
|
||||
{
|
||||
return pimpl->getGroup();
|
||||
}
|
||||
|
||||
ump::EndpointId MidiInput::getEndpointId() const
|
||||
{
|
||||
return pimpl->getEndpointId();
|
||||
}
|
||||
|
||||
void MidiInput::addCallback (MidiInputCallback& callback)
|
||||
{
|
||||
pimpl->addCallback (callback);
|
||||
}
|
||||
|
||||
void MidiInput::removeCallback (MidiInputCallback& callback)
|
||||
{
|
||||
pimpl->removeCallback (callback);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier)
|
||||
: Thread (SystemStats::getJUCEVersion() + ": midi out"), deviceInfo (deviceName, deviceIdentifier)
|
||||
MidiOutput::MidiOutput (std::shared_ptr<ump::Session> s,
|
||||
ump::Output x,
|
||||
uint8_t g,
|
||||
const MidiDeviceInfo& i,
|
||||
ump::LegacyVirtualOutput v)
|
||||
: session (s),
|
||||
virtualEndpoint (std::move (v)),
|
||||
connection (std::move (x)),
|
||||
storedInfo (i),
|
||||
group (g)
|
||||
{
|
||||
}
|
||||
|
||||
void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer)
|
||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
|
||||
{
|
||||
for (const auto metadata : buffer)
|
||||
sendMessageNow (metadata.getMessage());
|
||||
Array<MidiDeviceInfo> result;
|
||||
MidiDeviceListConnectionBroadcaster::get().getAllMidiDeviceInfo (ump::IOKind::dst, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer,
|
||||
double millisecondCounterToStartAt,
|
||||
double samplesPerSecondForBuffer)
|
||||
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
|
||||
{
|
||||
// You've got to call startBackgroundThread() for this to actually work..
|
||||
jassert (isThreadRunning());
|
||||
const auto address = MidiDeviceListConnectionBroadcaster::get().getEndpointGroupForId (ump::IOKind::dst, deviceIdentifier);
|
||||
|
||||
// this needs to be a value in the future - RTFM for this method!
|
||||
jassert (millisecondCounterToStartAt > 0);
|
||||
if (! address.has_value())
|
||||
return {};
|
||||
|
||||
auto timeScaleFactor = 1000.0 / samplesPerSecondForBuffer;
|
||||
const auto info = MidiDeviceListConnectionBroadcaster::get().getInfoForId (ump::IOKind::dst, deviceIdentifier);
|
||||
|
||||
for (const auto metadata : buffer)
|
||||
{
|
||||
auto eventTime = millisecondCounterToStartAt + timeScaleFactor * metadata.samplePosition;
|
||||
auto* m = new PendingMessage (metadata.data, metadata.numBytes, eventTime);
|
||||
if (! info.has_value())
|
||||
return {};
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
auto session = getLegacySession();
|
||||
|
||||
if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime)
|
||||
{
|
||||
m->next = firstMessage;
|
||||
firstMessage = m;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* mm = firstMessage;
|
||||
if (session == nullptr)
|
||||
return {};
|
||||
|
||||
while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime)
|
||||
mm = mm->next;
|
||||
auto connection = session->connectOutput (address->endpointId);
|
||||
|
||||
m->next = mm->next;
|
||||
mm->next = m;
|
||||
}
|
||||
}
|
||||
if (! connection.isAlive())
|
||||
return {};
|
||||
|
||||
notify();
|
||||
return rawToUniquePtr (new MidiOutput (session, std::move (connection), address->group, *info, {}));
|
||||
}
|
||||
|
||||
void MidiOutput::clearAllPendingMessages()
|
||||
std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& name)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
auto session = getLegacySession();
|
||||
|
||||
while (firstMessage != nullptr)
|
||||
{
|
||||
auto* m = firstMessage;
|
||||
firstMessage = firstMessage->next;
|
||||
delete m;
|
||||
}
|
||||
if (! session)
|
||||
return {};
|
||||
|
||||
auto port = session->createLegacyVirtualOutput (name);
|
||||
|
||||
if (! port)
|
||||
return {};
|
||||
|
||||
jassert (isValidMidi1VirtualEndpoint (ump::Endpoints::getInstance()->getEndpoint (port.getId()),
|
||||
ump::BlockDirection::sender));
|
||||
|
||||
auto connection = session->connectOutput (port.getId());
|
||||
|
||||
if (! connection)
|
||||
return {};
|
||||
|
||||
return rawToUniquePtr (new MidiOutput (session, std::move (connection), 0, { name, {} }, std::move (port)));
|
||||
}
|
||||
|
||||
void MidiOutput::startBackgroundThread()
|
||||
MidiDeviceInfo MidiOutput::getDeviceInfo() const noexcept
|
||||
{
|
||||
startThread (Priority::high);
|
||||
}
|
||||
|
||||
void MidiOutput::stopBackgroundThread()
|
||||
{
|
||||
stopThread (5000);
|
||||
}
|
||||
|
||||
void MidiOutput::run()
|
||||
{
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
auto now = Time::getMillisecondCounter();
|
||||
uint32 eventTime = 0;
|
||||
uint32 timeToWait = 500;
|
||||
|
||||
PendingMessage* message;
|
||||
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
message = firstMessage;
|
||||
|
||||
if (message != nullptr)
|
||||
{
|
||||
eventTime = (uint32) roundToInt (message->message.getTimeStamp());
|
||||
|
||||
if (eventTime > now + 20)
|
||||
{
|
||||
timeToWait = eventTime - (now + 20);
|
||||
message = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstMessage = message->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message != nullptr)
|
||||
{
|
||||
std::unique_ptr<PendingMessage> messageDeleter (message);
|
||||
|
||||
if (eventTime > now)
|
||||
{
|
||||
Time::waitForMillisecondCounter (eventTime);
|
||||
|
||||
if (threadShouldExit())
|
||||
break;
|
||||
}
|
||||
|
||||
if (eventTime > now - 200)
|
||||
sendMessageNow (message->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassert (timeToWait < 1000 * 30);
|
||||
wait ((int) timeToWait);
|
||||
}
|
||||
}
|
||||
|
||||
clearAllPendingMessages();
|
||||
return customName.has_value() ? storedInfo.withName (*customName) : storedInfo;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -35,8 +35,6 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
class MidiDeviceListConnectionBroadcaster;
|
||||
|
||||
/**
|
||||
To find out when the available MIDI devices change, call MidiDeviceListConnection::make(),
|
||||
passing a lambda that will be called on each configuration change.
|
||||
|
|
@ -67,22 +65,6 @@ public:
|
|||
*/
|
||||
MidiDeviceListConnection() = default;
|
||||
|
||||
MidiDeviceListConnection (const MidiDeviceListConnection&) = delete;
|
||||
MidiDeviceListConnection (MidiDeviceListConnection&& other) noexcept
|
||||
: broadcaster (std::exchange (other.broadcaster, nullptr)),
|
||||
key (std::exchange (other.key, Key{}))
|
||||
{
|
||||
}
|
||||
|
||||
MidiDeviceListConnection& operator= (const MidiDeviceListConnection&) = delete;
|
||||
MidiDeviceListConnection& operator= (MidiDeviceListConnection&& other) noexcept
|
||||
{
|
||||
MidiDeviceListConnection (std::move (other)).swap (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~MidiDeviceListConnection() noexcept;
|
||||
|
||||
/** Clears this connection.
|
||||
|
||||
If this object had an active connection, that connection will be deactivated, and the
|
||||
|
|
@ -90,7 +72,7 @@ public:
|
|||
*/
|
||||
void reset() noexcept
|
||||
{
|
||||
MidiDeviceListConnection().swap (*this);
|
||||
token.reset();
|
||||
}
|
||||
|
||||
/** Registers a function to be called whenever the midi device list changes.
|
||||
|
|
@ -102,22 +84,12 @@ public:
|
|||
static MidiDeviceListConnection make (std::function<void()>);
|
||||
|
||||
private:
|
||||
MidiDeviceListConnection (MidiDeviceListConnectionBroadcaster* b, const Key k)
|
||||
: broadcaster (b), key (k) {}
|
||||
|
||||
void swap (MidiDeviceListConnection& other) noexcept
|
||||
{
|
||||
std::swap (other.broadcaster, broadcaster);
|
||||
std::swap (other.key, key);
|
||||
}
|
||||
|
||||
MidiDeviceListConnectionBroadcaster* broadcaster = nullptr;
|
||||
Key key = {};
|
||||
ErasedScopeGuard token;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This struct contains information about a MIDI input or output device.
|
||||
This struct contains information about a MIDI 1.0 input or output port.
|
||||
|
||||
You can get one of these structs by calling the static getAvailableDevices() or
|
||||
getDefaultDevice() methods of MidiInput and MidiOutput or by calling getDeviceInfo()
|
||||
|
|
@ -148,22 +120,29 @@ struct MidiDeviceInfo
|
|||
|
||||
/** The identifier for this device.
|
||||
|
||||
This will be provided by the OS and it's format will differ on different systems
|
||||
This will be provided by the OS and its format will differ on different systems
|
||||
e.g. on macOS it will be a number whereas on Windows it will be a long alphanumeric string.
|
||||
*/
|
||||
String identifier;
|
||||
|
||||
[[nodiscard]] MidiDeviceInfo withName (String x) const { return withMember (*this, &MidiDeviceInfo::name, x); }
|
||||
[[nodiscard]] MidiDeviceInfo withIdentifier (String x) const { return withMember (*this, &MidiDeviceInfo::identifier, x); }
|
||||
|
||||
//==============================================================================
|
||||
auto tie() const { return std::tie (name, identifier); }
|
||||
bool operator== (const MidiDeviceInfo& other) const noexcept { return tie() == other.tie(); }
|
||||
bool operator!= (const MidiDeviceInfo& other) const noexcept { return tie() != other.tie(); }
|
||||
bool operator== (const MidiDeviceInfo& other) const noexcept
|
||||
{
|
||||
const auto tie = [] (auto& x) { return std::tuple (x.name, x.identifier); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); }
|
||||
};
|
||||
|
||||
class MidiInputCallback;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a midi input device.
|
||||
Represents a midi input device using the old bytestream format.
|
||||
|
||||
To create one of these, use the static getAvailableDevices() method to find out what
|
||||
inputs are available, and then use the openDevice() method to try to open one.
|
||||
|
|
@ -175,6 +154,12 @@ class MidiInputCallback;
|
|||
class JUCE_API MidiInput final
|
||||
{
|
||||
public:
|
||||
MidiInput (MidiInput&&) = delete;
|
||||
|
||||
MidiInput& operator= (MidiInput&&) = delete;
|
||||
|
||||
~MidiInput();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a list of the available midi input devices.
|
||||
|
||||
|
|
@ -196,13 +181,14 @@ public:
|
|||
|
||||
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to
|
||||
find the available devices that can be opened
|
||||
@param callback the object that will receive the midi messages from this device
|
||||
@param callback the object that will receive the midi messages from this device,
|
||||
you can also add and remove receivers with
|
||||
addCallback() and removeCallback()
|
||||
|
||||
@see MidiInputCallback, getDevices
|
||||
*/
|
||||
static std::unique_ptr<MidiInput> openDevice (const String& deviceIdentifier, MidiInputCallback* callback);
|
||||
static std::unique_ptr<MidiInput> openDevice (const String& deviceIdentifier, MidiInputCallback* callback = nullptr);
|
||||
|
||||
#if JUCE_LINUX || JUCE_BSD || JUCE_MAC || JUCE_IOS || DOXYGEN
|
||||
/** This will try to create a new midi input device (only available on Linux, macOS and iOS).
|
||||
|
||||
This will attempt to create a new midi input device with the specified name for other
|
||||
|
|
@ -216,13 +202,9 @@ public:
|
|||
@param deviceName the name of the device to create
|
||||
@param callback the object that will receive the midi messages from this device
|
||||
*/
|
||||
static std::unique_ptr<MidiInput> createNewDevice (const String& deviceName, MidiInputCallback* callback);
|
||||
#endif
|
||||
static std::unique_ptr<MidiInput> createNewDevice (const String& deviceName, MidiInputCallback* callback = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
~MidiInput();
|
||||
|
||||
/** Starts the device running.
|
||||
|
||||
After calling this, the device will start sending midi messages to the MidiInputCallback
|
||||
|
|
@ -239,37 +221,37 @@ public:
|
|||
void stop();
|
||||
|
||||
/** Returns the MidiDeviceInfo struct containing some information about this device. */
|
||||
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; }
|
||||
MidiDeviceInfo getDeviceInfo() const noexcept;
|
||||
|
||||
/** Returns the identifier of this device. */
|
||||
String getIdentifier() const noexcept { return deviceInfo.identifier; }
|
||||
String getIdentifier() const noexcept { return getDeviceInfo().identifier; }
|
||||
|
||||
/** Returns the name of this device. */
|
||||
String getName() const noexcept { return deviceInfo.name; }
|
||||
String getName() const noexcept { return getDeviceInfo().name; }
|
||||
|
||||
/** Sets a custom name for the device. */
|
||||
void setName (const String& newName) noexcept { deviceInfo.name = newName; }
|
||||
void setName (const String& newName) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** @cond */
|
||||
[[deprecated ("Use getAvailableDevices instead.")]]
|
||||
static StringArray getDevices();
|
||||
[[deprecated ("Use getDefaultDevice instead.")]]
|
||||
static int getDefaultDeviceIndex();
|
||||
[[deprecated ("Use openDevice that takes a device identifier instead.")]]
|
||||
static std::unique_ptr<MidiInput> openDevice (int, MidiInputCallback*);
|
||||
/** @endcond */
|
||||
/** In the case that this input refers to a specific group of a UMP input, this returns the
|
||||
index of the group.
|
||||
*/
|
||||
uint8_t getGroup() const;
|
||||
|
||||
/** @internal */
|
||||
class Pimpl;
|
||||
/** Returns the EndpointId that uniquely identifies the UMP endpoint that contains this input. */
|
||||
ump::EndpointId getEndpointId() const;
|
||||
|
||||
/** Adds an input listener. */
|
||||
void addCallback (MidiInputCallback&);
|
||||
|
||||
/** Removed an input listener. */
|
||||
void removeCallback (MidiInputCallback&);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
MidiInput();
|
||||
|
||||
//==============================================================================
|
||||
explicit MidiInput (const String&, const String&);
|
||||
|
||||
MidiDeviceInfo deviceInfo;
|
||||
|
||||
std::unique_ptr<Pimpl> internal;
|
||||
std::unique_ptr<Impl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput)
|
||||
};
|
||||
|
|
@ -316,15 +298,15 @@ public:
|
|||
The message passed in will contain the start of a sysex, but won't be finished
|
||||
with the terminating 0xf7 byte.
|
||||
*/
|
||||
virtual void handlePartialSysexMessage (MidiInput* source,
|
||||
const uint8* messageData,
|
||||
int numBytesSoFar,
|
||||
double timestamp);
|
||||
virtual void handlePartialSysexMessage ([[maybe_unused]] MidiInput* source,
|
||||
[[maybe_unused]] const uint8* messageData,
|
||||
[[maybe_unused]] int numBytesSoFar,
|
||||
[[maybe_unused]] double timestamp) {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a midi output device.
|
||||
Represents a midi output device using the old bytestream format.
|
||||
|
||||
To create one of these, use the static getAvailableDevices() method to find out what
|
||||
outputs are available, and then use the openDevice() method to try to open one.
|
||||
|
|
@ -333,7 +315,7 @@ public:
|
|||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API MidiOutput final : private Thread
|
||||
class JUCE_API MidiOutput final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
|
|
@ -346,7 +328,10 @@ public:
|
|||
static Array<MidiDeviceInfo> getAvailableDevices();
|
||||
|
||||
/** Returns the MidiDeviceInfo of the default midi output device to use. */
|
||||
static MidiDeviceInfo getDefaultDevice();
|
||||
static MidiDeviceInfo getDefaultDevice()
|
||||
{
|
||||
return getAvailableDevices().getFirst();
|
||||
}
|
||||
|
||||
/** Tries to open one of the midi output devices.
|
||||
|
||||
|
|
@ -361,7 +346,6 @@ public:
|
|||
*/
|
||||
static std::unique_ptr<MidiOutput> openDevice (const String& deviceIdentifier);
|
||||
|
||||
#if JUCE_LINUX || JUCE_BSD || JUCE_MAC || JUCE_IOS || DOXYGEN
|
||||
/** This will try to create a new midi output device (only available on Linux, macOS and iOS).
|
||||
|
||||
This will attempt to create a new midi output device with the specified name that other
|
||||
|
|
@ -375,30 +359,45 @@ public:
|
|||
@param deviceName the name of the device to create
|
||||
*/
|
||||
static std::unique_ptr<MidiOutput> createNewDevice (const String& deviceName);
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
~MidiOutput() override;
|
||||
|
||||
/** Returns the MidiDeviceInfo struct containing some information about this device. */
|
||||
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; }
|
||||
MidiDeviceInfo getDeviceInfo() const noexcept;
|
||||
|
||||
/** Returns the identifier of this device. */
|
||||
String getIdentifier() const noexcept { return deviceInfo.identifier; }
|
||||
String getIdentifier() const noexcept { return getDeviceInfo().identifier; }
|
||||
|
||||
/** Returns the name of this device. */
|
||||
String getName() const noexcept { return deviceInfo.name; }
|
||||
String getName() const noexcept { return getDeviceInfo().name; }
|
||||
|
||||
/** Sets a custom name for the device. */
|
||||
void setName (const String& newName) noexcept { deviceInfo.name = newName; }
|
||||
void setName (const String& newName) noexcept { customName = newName; }
|
||||
|
||||
/** In the case that this output refers to a specific group of a UMP output, this returns the
|
||||
index of the group.
|
||||
*/
|
||||
uint8_t getGroup() const { return group; }
|
||||
|
||||
/** Returns the EndpointId that uniquely identifies the UMP endpoint that contains this output. */
|
||||
ump::EndpointId getEndpointId() const { return connection.getEndpointId(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Sends out a MIDI message immediately. */
|
||||
void sendMessageNow (const MidiMessage& message);
|
||||
void sendMessageNow (const MidiMessage& message)
|
||||
{
|
||||
converter.convert ({ group, message.asSpan() }, [this] (const ump::View& view)
|
||||
{
|
||||
ump::Iterator b (view.data(), view.size());
|
||||
auto e = std::next (b);
|
||||
connection.send (b, e);
|
||||
});
|
||||
}
|
||||
|
||||
/** Sends out a sequence of MIDI messages immediately. */
|
||||
void sendBlockOfMessagesNow (const MidiBuffer& buffer);
|
||||
void sendBlockOfMessagesNow (const MidiBuffer& buffer)
|
||||
{
|
||||
for (const auto metadata : buffer)
|
||||
sendMessageNow (metadata.getMessage());
|
||||
}
|
||||
|
||||
/** This lets you supply a block of messages that will be sent out at some point
|
||||
in the future.
|
||||
|
|
@ -419,62 +418,60 @@ public:
|
|||
*/
|
||||
void sendBlockOfMessages (const MidiBuffer& buffer,
|
||||
double millisecondCounterToStartAt,
|
||||
double samplesPerSecondForBuffer);
|
||||
double samplesPerSecondForBuffer)
|
||||
{
|
||||
// This needs to be a value in the future - check the documentation for this function!
|
||||
jassert (millisecondCounterToStartAt > 0);
|
||||
|
||||
/** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */
|
||||
void clearAllPendingMessages();
|
||||
const auto timeScaleFactor = 1000.0 / samplesPerSecondForBuffer;
|
||||
|
||||
for (const auto item : buffer)
|
||||
{
|
||||
auto msg = item.getMessage();
|
||||
msg.setTimeStamp (millisecondCounterToStartAt + timeScaleFactor * msg.getTimeStamp());
|
||||
outputThread.addEvent (msg);
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets rid of any midi messages that had been added by sendBlockOfMessages().
|
||||
*/
|
||||
void clearAllPendingMessages() { outputThread.clearAllPendingMessages(); }
|
||||
|
||||
/** Starts up a background thread so that the device can send blocks of data.
|
||||
Call this to get the device ready, before using sendBlockOfMessages().
|
||||
*/
|
||||
void startBackgroundThread();
|
||||
void startBackgroundThread() { outputThread.start(); }
|
||||
|
||||
/** Stops the background thread, and clears any pending midi events.
|
||||
@see startBackgroundThread
|
||||
*/
|
||||
void stopBackgroundThread();
|
||||
void stopBackgroundThread() { outputThread.stop(); }
|
||||
|
||||
/** Returns true if the background thread used to send blocks of data is running.
|
||||
|
||||
@see startBackgroundThread, stopBackgroundThread
|
||||
*/
|
||||
bool isBackgroundThreadRunning() const noexcept { return isThreadRunning(); }
|
||||
|
||||
//==============================================================================
|
||||
/** @cond */
|
||||
[[deprecated ("Use getAvailableDevices instead.")]]
|
||||
static StringArray getDevices();
|
||||
[[deprecated ("Use getDefaultDevice instead.")]]
|
||||
static int getDefaultDeviceIndex();
|
||||
[[deprecated ("Use openDevice that takes a device identifier instead.")]]
|
||||
static std::unique_ptr<MidiOutput> openDevice (int);
|
||||
/** @endcond */
|
||||
|
||||
/** @internal */
|
||||
class Pimpl;
|
||||
bool isBackgroundThreadRunning() const { return outputThread.isRunning(); }
|
||||
|
||||
private:
|
||||
MidiOutput (std::shared_ptr<ump::Session>,
|
||||
ump::Output,
|
||||
uint8_t,
|
||||
const MidiDeviceInfo&,
|
||||
ump::LegacyVirtualOutput);
|
||||
|
||||
//==============================================================================
|
||||
struct PendingMessage
|
||||
std::shared_ptr<ump::Session> session;
|
||||
ump::LegacyVirtualOutput virtualEndpoint;
|
||||
std::optional<String> customName;
|
||||
ump::Output connection;
|
||||
MidiDeviceInfo storedInfo;
|
||||
ump::ToUMP1Converter converter;
|
||||
uint8_t group{};
|
||||
ScheduledEventThread<MidiMessage> outputThread { [this] (const MidiMessage& message)
|
||||
{
|
||||
PendingMessage (const void* data, int len, double timeStamp)
|
||||
: message (data, len, timeStamp)
|
||||
{
|
||||
}
|
||||
|
||||
MidiMessage message;
|
||||
PendingMessage* next;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
explicit MidiOutput (const String&, const String&);
|
||||
void run() override;
|
||||
|
||||
MidiDeviceInfo deviceInfo;
|
||||
|
||||
std::unique_ptr<Pimpl> internal;
|
||||
|
||||
CriticalSection lock;
|
||||
PendingMessage* firstMessage = nullptr;
|
||||
sendMessageNow (message);
|
||||
} };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput)
|
||||
};
|
||||
|
|
|
|||
164
modules/juce_audio_devices/midi_io/juce_ScheduledEventThread.h
Normal file
164
modules/juce_audio_devices/midi_io/juce_ScheduledEventThread.h
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
@internal
|
||||
|
||||
Allows events to be queued up, then for each event calls the OutputCallback at the time
|
||||
dictated by that event's timestamp.
|
||||
|
||||
Event must have a getTimeStamp() member function that returns the output time of the event.
|
||||
*/
|
||||
template <typename Event>
|
||||
class ScheduledEventThread : private Thread
|
||||
{
|
||||
public:
|
||||
using OutputCallback = std::function<void (const Event&)>;
|
||||
|
||||
explicit ScheduledEventThread (OutputCallback&& c)
|
||||
: Thread (SystemStats::getJUCEVersion() + ": MIDI Out"),
|
||||
outputCallback (std::move (c))
|
||||
{
|
||||
jassert (outputCallback != nullptr);
|
||||
}
|
||||
|
||||
~ScheduledEventThread() override
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void clearAllPendingMessages()
|
||||
{
|
||||
{
|
||||
const std::scoped_lock sl (mutex);
|
||||
pendingMessages.clear();
|
||||
}
|
||||
|
||||
condvar.notify_one();
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
{
|
||||
const std::scoped_lock sl (mutex);
|
||||
backgroundThreadRunning = true;
|
||||
}
|
||||
|
||||
startThread (Priority::high);
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
{
|
||||
const std::scoped_lock sl (mutex);
|
||||
backgroundThreadRunning = false;
|
||||
}
|
||||
|
||||
condvar.notify_one();
|
||||
stopThread (-1);
|
||||
}
|
||||
|
||||
void addEvent (const Event& event)
|
||||
{
|
||||
// You've got to call startBackgroundThread() for this to actually work.
|
||||
jassert (isThreadRunning());
|
||||
|
||||
{
|
||||
const std::scoped_lock sl (mutex);
|
||||
pendingMessages.insert (event);
|
||||
}
|
||||
|
||||
condvar.notify_one();
|
||||
}
|
||||
|
||||
bool isRunning() const
|
||||
{
|
||||
const std::scoped_lock sl (mutex);
|
||||
return backgroundThreadRunning;
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
std::unique_lock lock (mutex);
|
||||
condvar.wait (lock, [&]
|
||||
{
|
||||
return ! pendingMessages.empty() || ! backgroundThreadRunning;
|
||||
});
|
||||
|
||||
if (! backgroundThreadRunning)
|
||||
return;
|
||||
|
||||
const auto now = Time::getMillisecondCounter();
|
||||
const auto event = *pendingMessages.begin();
|
||||
pendingMessages.erase (pendingMessages.begin());
|
||||
|
||||
const auto timestamp = event.getTimeStamp();
|
||||
|
||||
if (timestamp > now + 20)
|
||||
{
|
||||
const auto millis = static_cast<int64_t> (timestamp - (now + 20));
|
||||
condvar.wait_for (lock, std::chrono::milliseconds (millis));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timestamp > now)
|
||||
Time::waitForMillisecondCounter ((uint32) timestamp);
|
||||
|
||||
if (timestamp > now - 200)
|
||||
outputCallback (event);
|
||||
}
|
||||
}
|
||||
|
||||
struct Comparator
|
||||
{
|
||||
bool operator() (const Event& a, const Event& b) const
|
||||
{
|
||||
return a.getTimeStamp() < b.getTimeStamp();
|
||||
}
|
||||
};
|
||||
|
||||
mutable std::mutex mutex;
|
||||
std::condition_variable condvar;
|
||||
std::multiset<Event, Comparator> pendingMessages;
|
||||
OutputCallback outputCallback;
|
||||
bool backgroundThreadRunning = false;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
159
modules/juce_audio_devices/midi_io/ump/juce_UMPBlock.h
Normal file
159
modules/juce_audio_devices/midi_io/ump/juce_UMPBlock.h
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a Function Block (FB) or Group Terminal Block (GTB).
|
||||
|
||||
GTBs are only available in USB MIDI, so devices on non-USB transports will not expose GTB
|
||||
information. In the case that a device only exposes GTBs and not FBs, default values will
|
||||
be used for any fields that are unavailable in the GTB definition.
|
||||
|
||||
@see BlockInfo
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Block
|
||||
{
|
||||
public:
|
||||
Block() = default;
|
||||
|
||||
[[nodiscard]] Block withName (const String& x) const { return withMember (*this, &Block::name, x); }
|
||||
|
||||
[[nodiscard]] Block withFirstGroup (uint8_t x) const { return withMember (*this, &Block::info, info.withFirstGroup (x)); }
|
||||
[[nodiscard]] Block withNumGroups (uint8_t x) const { return withMember (*this, &Block::info, info.withNumGroups (x)); }
|
||||
[[nodiscard]] Block withMaxSysex8Streams (uint8_t x) const { return withMember (*this, &Block::info, info.withMaxSysex8Streams (x)); }
|
||||
[[nodiscard]] Block withEnabled (bool x = true) const { return withMember (*this, &Block::info, info.withEnabled (x)); }
|
||||
|
||||
[[nodiscard]] Block withUiHint (BlockUiHint x) const
|
||||
{
|
||||
return withMember (*this, &Block::info, info.withUiHint (x));
|
||||
}
|
||||
|
||||
[[nodiscard]] Block withMIDI1ProxyKind (BlockMIDI1ProxyKind x) const
|
||||
{
|
||||
return withMember (*this, &Block::info, info.withMIDI1ProxyKind (x));
|
||||
}
|
||||
|
||||
[[nodiscard]] Block withDirection (BlockDirection x) const
|
||||
{
|
||||
return withMember (*this, &Block::info, info.withDirection (x));
|
||||
}
|
||||
|
||||
/** The name of the block, if any. */
|
||||
String getName() const { return name; }
|
||||
|
||||
/** All info relating to this block. */
|
||||
BlockInfo getInfo() const { return info; }
|
||||
|
||||
/** In the range 0x0 to 0xF inclusive. */
|
||||
uint8_t getFirstGroup() const
|
||||
{
|
||||
return info.getFirstGroup();
|
||||
}
|
||||
|
||||
/** In the range 0x01 to 0x10 inclusive. */
|
||||
uint8_t getNumGroups() const
|
||||
{
|
||||
return info.getNumGroups();
|
||||
}
|
||||
|
||||
/** The number of simultaneous Sysex8 streams that are supported. */
|
||||
uint8_t getMaxSysex8Streams() const
|
||||
{
|
||||
return info.getMaxSysex8Streams();
|
||||
}
|
||||
|
||||
/** Some blocks may support bidirectional communication (e.g. for CI) but function predominantly
|
||||
as a sender or receiver. The direction returned here is a hint to the user that doesn't
|
||||
necessarily reflect the hardware capabilities.
|
||||
*/
|
||||
BlockUiHint getUiHint() const
|
||||
{
|
||||
return info.getUiHint();
|
||||
}
|
||||
|
||||
/** If this block is a proxy for a MIDI 1.0 stream, describes the capabilities of that stream. */
|
||||
BlockMIDI1ProxyKind getMIDI1ProxyKind() const
|
||||
{
|
||||
return info.getMIDI1ProxyKind();
|
||||
}
|
||||
|
||||
/** Returns the message transmission directions that are supported by this block.
|
||||
Use this to determine block capabilities.
|
||||
For information that will be displayed to the user, prefer getUIHint().
|
||||
*/
|
||||
BlockDirection getDirection() const
|
||||
{
|
||||
return info.getDirection();
|
||||
}
|
||||
|
||||
/** True if this block is enabled. */
|
||||
bool isEnabled() const
|
||||
{
|
||||
return info.isEnabled();
|
||||
}
|
||||
|
||||
/** True if the names of the two blocks match. */
|
||||
bool nameMatches (const Block& other) const
|
||||
{
|
||||
return name == other.name;
|
||||
}
|
||||
|
||||
/** True if the BlockInfo of the two blocks match. */
|
||||
bool infoMatches (const Block& other) const
|
||||
{
|
||||
return info == other.info;
|
||||
}
|
||||
|
||||
bool operator== (const Block& other) const
|
||||
{
|
||||
const auto tie = [] (auto& x) { return std::tuple (x.info, x.name); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const Block& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
private:
|
||||
BlockInfo info;
|
||||
String name;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An interface class for entities that are interested in disconnection notifications.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
struct DisconnectionListener
|
||||
{
|
||||
DisconnectionListener() = default;
|
||||
|
||||
DisconnectionListener (const DisconnectionListener&) = default;
|
||||
DisconnectionListener (DisconnectionListener&&) noexcept = default;
|
||||
|
||||
DisconnectionListener& operator= (const DisconnectionListener&) = default;
|
||||
DisconnectionListener& operator= (DisconnectionListener&&) noexcept = default;
|
||||
|
||||
virtual ~DisconnectionListener() = default;
|
||||
|
||||
/** Called to notify that the system has destroyed/disconnected a resource. */
|
||||
virtual void disconnected() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
187
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpoint.h
Normal file
187
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpoint.h
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a single MIDI endpoint, which may have up to one input and up to one output.
|
||||
|
||||
An Endpoint object just holds a snapshot of the physical endpoint's last known state at the
|
||||
point when the Endpoint instance was created.
|
||||
Instead of storing Endpoint instances, it's a better idea to store an EndpointId, and to call
|
||||
Endpoints::getEndpoint() to get an up-to-date snapshot.
|
||||
|
||||
To connect to an endpoint, use Session::connectInput() or Session::connectOutput() to create
|
||||
a connection in the context of a particular session.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Endpoint
|
||||
{
|
||||
public:
|
||||
Endpoint() = default;
|
||||
|
||||
[[nodiscard]] Endpoint withName (String x) const { return withMember (*this, &Endpoint::name, x); }
|
||||
[[nodiscard]] Endpoint withProtocol (std::optional<PacketProtocol> x) const { return withMember (*this, &Endpoint::streamConfig, StreamConfigFlags ((streamConfig & ~maskProtocol) | asFlags (x))); }
|
||||
[[nodiscard]] Endpoint withDeviceInfo (DeviceInfo x) const { return withMember (*this, &Endpoint::deviceInfo, std::move (x)); }
|
||||
[[nodiscard]] Endpoint withProductInstanceId (String x) const { return withMember (*this, &Endpoint::productInstanceId, std::move (x)); }
|
||||
[[nodiscard]] Endpoint withUMPVersion (uint8_t major, uint8_t minor) const { return withMember (*this, &Endpoint::endpointInfo, endpointInfo.withVersion (major, minor)); }
|
||||
[[nodiscard]] Endpoint withStaticBlocks (bool x = true) const { return withMember (*this, &Endpoint::endpointInfo, endpointInfo.withStaticFunctionBlocks (x)); }
|
||||
[[nodiscard]] Endpoint withMidi1Support (bool x = true) const { return withMember (*this, &Endpoint::endpointInfo, endpointInfo.withMidi1Support (x)); }
|
||||
[[nodiscard]] Endpoint withMidi2Support (bool x = true) const { return withMember (*this, &Endpoint::endpointInfo, endpointInfo.withMidi2Support (x)); }
|
||||
[[nodiscard]] Endpoint withReceiveJRSupport (bool x = true) const { return withMember (*this, &Endpoint::endpointInfo, endpointInfo.withReceiveJRSupport (x)); }
|
||||
[[nodiscard]] Endpoint withTransmitJRSupport (bool x = true) const { return withMember (*this, &Endpoint::endpointInfo, endpointInfo.withTransmitJRSupport (x)); }
|
||||
|
||||
[[nodiscard]] Endpoint withReceiveJREnabled (bool x = true) const { return withMember (*this, &Endpoint::streamConfig, StreamConfigFlags (x ? (streamConfig | maskRxjr) : (streamConfig & ~maskRxjr))); }
|
||||
[[nodiscard]] Endpoint withTransmitJREnabled (bool x = true) const { return withMember (*this, &Endpoint::streamConfig, StreamConfigFlags (x ? (streamConfig | maskTxjr) : (streamConfig & ~maskTxjr))); }
|
||||
|
||||
/** The block index is used to uniquely identify the block, so be sure to always declare
|
||||
blocks in a consistent order.
|
||||
*/
|
||||
[[nodiscard]] Endpoint withBlocks (Span<const Block> x) const
|
||||
{
|
||||
auto result = withNumBlocks (x.size());
|
||||
std::copy (x.begin(), x.end(), result.blocks.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
/** The number of blocks on this endpoint. */
|
||||
[[nodiscard]] Endpoint withNumBlocks (size_t x) const
|
||||
{
|
||||
jassert (x <= blocks.size());
|
||||
auto result = *this;
|
||||
result.endpointInfo = result.endpointInfo.withNumFunctionBlocks ((uint8_t) std::min (blocks.size(), x));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the name of this endpoint. */
|
||||
String getName() const { return name; }
|
||||
|
||||
/** Returns properties of the device that owns the endpoint. */
|
||||
DeviceInfo getDeviceInfo() const
|
||||
{
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
/** Returns the product instance ID if available, or an empty string otherwise.
|
||||
The product instance ID should match the device serial number, and should be unique per
|
||||
manufacturer, family, and model.
|
||||
|
||||
This ID can be used to distinguish between separate devices that have the same DeviceInfo.
|
||||
It can also be used to determine whether separate endpoints are associated with the same
|
||||
device.
|
||||
*/
|
||||
String getProductInstanceId() const
|
||||
{
|
||||
return productInstanceId;
|
||||
}
|
||||
|
||||
/** The protocol that the endpoint currently expects to send and receive; endpoints are allowed
|
||||
to switch protocols, so this won't always return the same value.
|
||||
May return nullopt if the protocol is unknown, perhaps because negotiation has not taken place.
|
||||
*/
|
||||
std::optional<PacketProtocol> getProtocol() const
|
||||
{
|
||||
switch (streamConfig & maskProtocol)
|
||||
{
|
||||
case 1: return ump::PacketProtocol::MIDI_1_0;
|
||||
case 2: return ump::PacketProtocol::MIDI_2_0;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
uint8_t getUMPVersionMajor() const { return endpointInfo.getVersionMajor(); }
|
||||
uint8_t getUMPVersionMinor() const { return endpointInfo.getVersionMinor(); }
|
||||
bool hasStaticBlocks() const { return endpointInfo.hasStaticFunctionBlocks(); }
|
||||
bool hasMidi1Support() const { return endpointInfo.hasMidi1Support(); }
|
||||
bool hasMidi2Support() const { return endpointInfo.hasMidi2Support(); }
|
||||
bool hasReceiveJRSupport() const { return endpointInfo.hasReceiveJRSupport(); }
|
||||
bool hasTransmitJRSupport() const { return endpointInfo.hasTransmitJRSupport(); }
|
||||
|
||||
bool isReceiveJREnabled() const { return streamConfig & maskRxjr; }
|
||||
bool isTransmitJREnabled() const { return streamConfig & maskTxjr; }
|
||||
|
||||
/** There can be a maximum of 32 blocks.
|
||||
This may return an empty span if the endpoint has neither function blocks nor group terminal
|
||||
blocks.
|
||||
*/
|
||||
Span<const Block> getBlocks() const&
|
||||
{
|
||||
return { blocks.data(), endpointInfo.getNumFunctionBlocks() };
|
||||
}
|
||||
|
||||
/** Returns a mutable view over the blocks in this endpoint. */
|
||||
Span<Block> getBlocks() &
|
||||
{
|
||||
return { blocks.data(), endpointInfo.getNumFunctionBlocks() };
|
||||
}
|
||||
|
||||
// These are deleted because it's probably not a good idea to create a span over a temporary
|
||||
Span<const Block> getBlocks() const&& = delete;
|
||||
Span<Block> getBlocks() && = delete;
|
||||
|
||||
private:
|
||||
enum StreamConfigFlags : uint16_t
|
||||
{
|
||||
maskProtocol = 0x00ff,
|
||||
maskTxjr = 0x0100,
|
||||
maskRxjr = 0x0200,
|
||||
};
|
||||
|
||||
static StreamConfigFlags asFlags (std::optional<PacketProtocol> p)
|
||||
{
|
||||
if (! p.has_value())
|
||||
return {};
|
||||
|
||||
switch (*p)
|
||||
{
|
||||
case ump::PacketProtocol::MIDI_1_0: return StreamConfigFlags { 1 };
|
||||
case ump::PacketProtocol::MIDI_2_0: return StreamConfigFlags { 2 };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::array<Block, 32> blocks;
|
||||
String name;
|
||||
String productInstanceId;
|
||||
EndpointInfo endpointInfo;
|
||||
DeviceInfo deviceInfo;
|
||||
StreamConfigFlags streamConfig{};
|
||||
};
|
||||
|
||||
}
|
||||
96
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpointId.h
Normal file
96
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpointId.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/** Directional properties of a MIDI endpoint. */
|
||||
enum class IOKind : uint8_t
|
||||
{
|
||||
src, ///< A source of MIDI events
|
||||
dst, ///< A destination for MIDI events
|
||||
};
|
||||
|
||||
/** All possible MIDI directions. */
|
||||
constexpr IOKind ioKinds[] { IOKind::src, IOKind::dst };
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Identifies a MIDI endpoint. This is intended to be an opaque type that can only be compared with
|
||||
other instances.
|
||||
|
||||
For backwards compatibility, we need to ensure that port identifier strings that used to work
|
||||
with MidiInput and MidiOutput continue to function in the same way. However, the old identifiers
|
||||
weren't necessarily unique between inputs and outputs (a MIDI 1.0 input and output could have
|
||||
the same ID), which means that a single id string isn't enough to uniquely identify an input
|
||||
or output port.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class EndpointId
|
||||
{
|
||||
auto tie() const { return std::tuple (src, dst); }
|
||||
|
||||
public:
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
EndpointId() = default;
|
||||
|
||||
bool operator== (const EndpointId& x) const { return tie() == x.tie(); }
|
||||
bool operator!= (const EndpointId& x) const { return tie() != x.tie(); }
|
||||
bool operator< (const EndpointId& x) const { return tie() < x.tie(); }
|
||||
bool operator<= (const EndpointId& x) const { return tie() <= x.tie(); }
|
||||
bool operator> (const EndpointId& x) const { return tie() > x.tie(); }
|
||||
bool operator>= (const EndpointId& x) const { return tie() >= x.tie(); }
|
||||
|
||||
String get (IOKind k) const { return k == IOKind::src ? src : dst; }
|
||||
|
||||
static EndpointId make (IOKind dir, const String& id)
|
||||
{
|
||||
return dir == IOKind::src ? makeSrcDst (id, {}) : makeSrcDst ({}, id);
|
||||
}
|
||||
|
||||
static EndpointId makeSrcDst (const String& s, const String& d)
|
||||
{
|
||||
return { s, d };
|
||||
}
|
||||
|
||||
String src, dst;
|
||||
|
||||
private:
|
||||
EndpointId (const String& s, const String& d) : src (s), dst (d) {}
|
||||
};
|
||||
|
||||
}
|
||||
243
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpoints.cpp
Normal file
243
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpoints.cpp
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
class Endpoints::Impl : private EndpointsListener
|
||||
{
|
||||
public:
|
||||
class Native
|
||||
{
|
||||
public:
|
||||
virtual ~Native() = default;
|
||||
virtual Backend getBackend() const = 0;
|
||||
virtual bool isVirtualMidiBytestreamServiceActive() const = 0;
|
||||
virtual bool isVirtualMidiUmpServiceActive() const = 0;
|
||||
virtual void setVirtualMidiBytestreamServiceActive (bool) {}
|
||||
virtual void setVirtualMidiUmpServiceActive (bool) {}
|
||||
virtual void getEndpoints (std::vector<EndpointId>&) const = 0;
|
||||
virtual std::optional<Endpoint> getEndpoint (const EndpointId&) const = 0;
|
||||
virtual std::optional<StaticDeviceInfo> getStaticDeviceInfo (const EndpointId&) const = 0;
|
||||
virtual std::unique_ptr<Session::Impl::Native> makeSession (const String&) = 0;
|
||||
|
||||
static std::unique_ptr<Native> make (EndpointsListener&);
|
||||
};
|
||||
|
||||
Backend getBackend() const
|
||||
{
|
||||
return native->getBackend();
|
||||
}
|
||||
|
||||
bool isVirtualMidiBytestreamServiceActive() const
|
||||
{
|
||||
return native->isVirtualMidiBytestreamServiceActive();
|
||||
}
|
||||
|
||||
bool isVirtualMidiUmpServiceActive() const
|
||||
{
|
||||
return native->isVirtualMidiUmpServiceActive();
|
||||
}
|
||||
|
||||
void setVirtualMidiBytestreamServiceActive (bool x)
|
||||
{
|
||||
native->setVirtualMidiBytestreamServiceActive (x);
|
||||
}
|
||||
|
||||
void setVirtualMidiUmpServiceActive (bool x)
|
||||
{
|
||||
native->setVirtualMidiUmpServiceActive (x);
|
||||
}
|
||||
|
||||
void getEndpoints (std::vector<EndpointId>& x) const
|
||||
{
|
||||
native->getEndpoints (x);
|
||||
}
|
||||
|
||||
std::optional<Endpoint> getEndpoint (const EndpointId& x) const
|
||||
{
|
||||
return native->getEndpoint (x);
|
||||
}
|
||||
|
||||
std::optional<StaticDeviceInfo> getStaticDeviceInfo (const EndpointId& x) const
|
||||
{
|
||||
return native->getStaticDeviceInfo (x);
|
||||
}
|
||||
|
||||
std::unique_ptr<Session::Impl::Native> makeSession (const String& x)
|
||||
{
|
||||
return native->makeSession (x);
|
||||
}
|
||||
|
||||
void addListener (EndpointsListener& x)
|
||||
{
|
||||
listeners.add (&x);
|
||||
}
|
||||
|
||||
void removeListener (EndpointsListener& x)
|
||||
{
|
||||
listeners.remove (&x);
|
||||
}
|
||||
|
||||
static std::unique_ptr<Impl> make()
|
||||
{
|
||||
auto result = rawToUniquePtr (new Impl);
|
||||
result->native = Native::make (*result);
|
||||
|
||||
if (result->native == nullptr)
|
||||
return {};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static String getGlobalMidiClientName()
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
return app->getApplicationName();
|
||||
|
||||
return "JUCE";
|
||||
}
|
||||
|
||||
private:
|
||||
void endpointsChanged() override
|
||||
{
|
||||
listeners.call ([] (auto& l) { l.endpointsChanged(); });
|
||||
}
|
||||
|
||||
void virtualMidiServiceActiveChanged() override
|
||||
{
|
||||
listeners.call ([] (auto& l) { l.virtualMidiServiceActiveChanged(); });
|
||||
}
|
||||
|
||||
Impl() = default;
|
||||
|
||||
ListenerList<EndpointsListener> listeners;
|
||||
std::unique_ptr<Native> native;
|
||||
};
|
||||
|
||||
void Endpoints::getEndpoints (std::vector<EndpointId>& x) const
|
||||
{
|
||||
x.clear();
|
||||
|
||||
if (impl != nullptr)
|
||||
impl->getEndpoints (x);
|
||||
}
|
||||
|
||||
std::vector<EndpointId> Endpoints::getEndpoints() const
|
||||
{
|
||||
std::vector<EndpointId> result;
|
||||
getEndpoints (result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Endpoint> Endpoints::getEndpoint (const EndpointId& x) const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getEndpoint (x);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<StaticDeviceInfo> Endpoints::getStaticDeviceInfo (const EndpointId& x) const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getStaticDeviceInfo (x);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Endpoints::addListener (EndpointsListener& x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
impl->addListener (x);
|
||||
}
|
||||
|
||||
void Endpoints::removeListener (EndpointsListener& x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
impl->addListener (x);
|
||||
}
|
||||
|
||||
Session Endpoints::makeSession (const String& x) const
|
||||
{
|
||||
return Session::Impl::makeSession (impl != nullptr ? impl->makeSession (x) : nullptr);
|
||||
}
|
||||
|
||||
std::optional<Backend> Endpoints::getBackend() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getBackend();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Endpoints::isVirtualMidiBytestreamServiceActive() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->isVirtualMidiBytestreamServiceActive();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Endpoints::isVirtualMidiUmpServiceActive() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->isVirtualMidiUmpServiceActive();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Endpoints::setVirtualMidiBytestreamServiceActive (bool x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
impl->setVirtualMidiBytestreamServiceActive (x);
|
||||
}
|
||||
|
||||
void Endpoints::setVirtualMidiUmpServiceActive (bool x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
impl->setVirtualMidiUmpServiceActive (x);
|
||||
}
|
||||
|
||||
Endpoints::Endpoints()
|
||||
: impl (Impl::make())
|
||||
{
|
||||
}
|
||||
|
||||
Endpoints::~Endpoints()
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
}
|
||||
277
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpoints.h
Normal file
277
modules/juce_audio_devices/midi_io/ump/juce_UMPEndpoints.h
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
An interface class for types that are interested in receiving updates
|
||||
about changes to available MIDI endpoints.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
struct EndpointsListener
|
||||
{
|
||||
EndpointsListener() = default;
|
||||
|
||||
EndpointsListener (const EndpointsListener&) = default;
|
||||
EndpointsListener (EndpointsListener&&) noexcept = default;
|
||||
|
||||
EndpointsListener& operator= (const EndpointsListener&) = default;
|
||||
EndpointsListener& operator= (EndpointsListener&&) noexcept = default;
|
||||
|
||||
virtual ~EndpointsListener() = default;
|
||||
|
||||
/** Called on each platform to notify listeners that some aspect of the MIDI configuration
|
||||
has changed, including device connection, disconnection, and property changes.
|
||||
*/
|
||||
virtual void endpointsChanged() = 0;
|
||||
|
||||
/** Called on Android to indicate that the session managing the virtual MIDI ports
|
||||
was started or stopped.
|
||||
Creating a virtual endpoint will fail if the service is not running, so you may wish to
|
||||
listen for this event, and to create the virtual ports after this function has been called.
|
||||
You can query the current state of the service using Endpoints::isVirtualUmpServiceActive()
|
||||
and Endpoints::isVirtualBytestreamServiceActive().
|
||||
*/
|
||||
virtual void virtualMidiServiceActiveChanged() {}
|
||||
};
|
||||
|
||||
/**
|
||||
Static information about a particular MIDI device that can be queried without opening a
|
||||
connection to the device.
|
||||
|
||||
This information differs from the detailed information in the Endpoint struct, in that
|
||||
the StaticDeviceInformation is communicated out-of-band, whereas Endpoint information is
|
||||
communicated 'in-band', i.e. it is sent as MIDI messages after establishing a connection to the
|
||||
device.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class StaticDeviceInfo
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] StaticDeviceInfo withName (const String& x) const { return withMember (*this, &StaticDeviceInfo::name, x); }
|
||||
[[nodiscard]] StaticDeviceInfo withManufacturer (const String& x) const { return withMember (*this, &StaticDeviceInfo::manufacturer, x); }
|
||||
[[nodiscard]] StaticDeviceInfo withProduct (const String& x) const { return withMember (*this, &StaticDeviceInfo::product, x); }
|
||||
[[nodiscard]] StaticDeviceInfo withHasSource (bool x) const { return withMember (*this, &StaticDeviceInfo::source, x); }
|
||||
[[nodiscard]] StaticDeviceInfo withHasDestination (bool x) const { return withMember (*this, &StaticDeviceInfo::destination, x); }
|
||||
[[nodiscard]] StaticDeviceInfo withTransport (Transport x) const { return withMember (*this, &StaticDeviceInfo::transport, x); }
|
||||
|
||||
[[nodiscard]] StaticDeviceInfo withLegacyIdentifiersSrc (Span<const String, 16> x) const
|
||||
{
|
||||
auto copy = *this;
|
||||
std::copy (x.begin(), x.end(), copy.identifierSrc.begin());
|
||||
return copy;
|
||||
}
|
||||
|
||||
[[nodiscard]] StaticDeviceInfo withLegacyIdentifiersDst (Span<const String, 16> x) const
|
||||
{
|
||||
auto copy = *this;
|
||||
std::copy (x.begin(), x.end(), copy.identifierDst.begin());
|
||||
return copy;
|
||||
}
|
||||
|
||||
[[nodiscard]] StaticDeviceInfo withLegacyIdentifiers (IOKind k, Span<const String, 16> x) const
|
||||
{
|
||||
return k == IOKind::src ? withLegacyIdentifiersSrc (x) : withLegacyIdentifiersDst (x);
|
||||
}
|
||||
|
||||
String getName() const { return name; }
|
||||
String getManufacturer() const { return manufacturer; }
|
||||
String getProduct() const { return product; }
|
||||
bool hasSource() const { return source; }
|
||||
bool hasDestination() const { return destination; }
|
||||
Transport getTransport() const { return transport; }
|
||||
|
||||
/** Returns an identifier to uniquely identify each group, for use with the legacy MIDI API. */
|
||||
Span<const String, 16> getLegacyIdentifiersSrc() const& { return identifierSrc; }
|
||||
/** Returns an identifier to uniquely identify each group, for use with the legacy MIDI API. */
|
||||
Span<const String, 16> getLegacyIdentifiersDst() const& { return identifierDst; }
|
||||
|
||||
Span<const String, 16> getLegacyIdentifiers (IOKind k) const&
|
||||
{
|
||||
return k == IOKind::src ? getLegacyIdentifiersSrc() : getLegacyIdentifiersDst();
|
||||
}
|
||||
|
||||
Span<const String, 16> getLegacyIdentifiersSrc() const&& = delete;
|
||||
Span<const String, 16> getLegacyIdentifiersDst() const&& = delete;
|
||||
Span<const String, 16> getLegacyIdentifiers (IOKind) const&& = delete;
|
||||
|
||||
private:
|
||||
std::array<String, 16> identifierSrc, identifierDst;
|
||||
String name; ///< The full human-readable name of this device
|
||||
String manufacturer; ///< The name of the organisation that produced this device
|
||||
String product; ///< The human-readable product name
|
||||
Transport transport{}; ///< The format used for MIDI messages in transit
|
||||
uint8_t source = false; ///< True if the device can send messages
|
||||
uint8_t destination = false; ///< True if the device can receive messages
|
||||
};
|
||||
|
||||
/** MIDI implementation technologies
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
enum class Backend
|
||||
{
|
||||
alsa, ///< linux
|
||||
android, ///< android
|
||||
coremidi, ///< macOS, iOS
|
||||
winmm, ///< classic Windows MIDI
|
||||
winrt, ///< Windows WinRT MIDI 1.0 (experimental)
|
||||
wms, ///< Windows MIDI Services (experimental)
|
||||
};
|
||||
|
||||
/**
|
||||
Endpoints known to the system.
|
||||
|
||||
Use this to locate hardware and software devices that are capable of sending and
|
||||
receiving MIDI messages.
|
||||
|
||||
Call makeSession() to create a session that manages connections to those devices.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Endpoints : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
/** Fetch endpoint ids. */
|
||||
std::vector<EndpointId> getEndpoints() const;
|
||||
|
||||
/** Fetch endpoint ids into the provided buffer. */
|
||||
void getEndpoints (std::vector<EndpointId>&) const;
|
||||
|
||||
/** Fetches information about a particular endpoint, or nullopt if the information
|
||||
is unavailable.
|
||||
|
||||
Currently, this function may return nullopt on Android for UMP devices that are
|
||||
connected but not opened.
|
||||
|
||||
If you're displaying endpoint names in a user-facing list, it's probably a good idea to
|
||||
use the Endpoint name if getEndpoint() returns a value. Otherwise, you can use the
|
||||
StaticDeviceInformation name if getStaticDeviceInformation() returns a value. If both
|
||||
functions return nullopt, this implies that the device is unknown/disconnected.
|
||||
*/
|
||||
std::optional<Endpoint> getEndpoint (const EndpointId&) const;
|
||||
|
||||
/** Fetches static information about a particular endpoint, or nullopt if the endpoint is
|
||||
unavailable, which might happen if the endpoint has been disconnected.
|
||||
*/
|
||||
std::optional<StaticDeviceInfo> getStaticDeviceInfo (const EndpointId&) const;
|
||||
|
||||
/** Adds a listener that will receive notifications when endpoints are added, removed,
|
||||
or otherwise changed.
|
||||
*/
|
||||
void addListener (EndpointsListener& l);
|
||||
|
||||
/** Removes a listener that was previously added with addListener().
|
||||
*/
|
||||
void removeListener (EndpointsListener&);
|
||||
|
||||
/** Creates a session to manage connections to endpoints.
|
||||
It's possible for this function to fail, in which case Session::isAlive() will return false.
|
||||
*/
|
||||
Session makeSession (const String& name) const;
|
||||
|
||||
/** Returns the technology that is being used to communicate with MIDI devices on this platform.
|
||||
This is mainly relevant on Windows, where there are several different MIDI implementations.
|
||||
|
||||
The intended use of this getter is to allow programs to detect whether the Windows MIDI
|
||||
Services (WMS) are active and running. If initialisation of WMS failed, the implementation
|
||||
will fall back to an earlier, more limited implementation. Programs may wish to detect this
|
||||
situation so that they can direct users to install the WMS SDK.
|
||||
|
||||
If no MIDI technology is available, this returns nullopt.
|
||||
*/
|
||||
std::optional<Backend> getBackend() const;
|
||||
|
||||
/** Creating a virtual legacy port will only succeed if this function returns true.
|
||||
On platforms that support virtual MIDI connections (macOS, iOS, ALSA, Windows MIDI Services),
|
||||
this will normally return true.
|
||||
On platforms that don't support virtual MIDI (older Windows MIDI APIs), this will always
|
||||
return false.
|
||||
On Android, this will return false until the virtual MIDI service has started, and will
|
||||
return true while the service is running.
|
||||
You can listen for virtualMidiServiceActiveChanged() to take actions (e.g. creating or
|
||||
destroying virtual ports) when the service status changes.
|
||||
*/
|
||||
bool isVirtualMidiBytestreamServiceActive() const;
|
||||
|
||||
/** Creating a virtual ump endpoint will only succeed if this function returns true.
|
||||
On recent platforms that support virtual MIDI connections (recent macOS and iOS, recent ALSA,
|
||||
Windows MIDI Services), this will normally return true.
|
||||
On platforms that don't support virtual MIDI (older Windows MIDI APIs), this will always
|
||||
return false.
|
||||
On Android, this will return false until the virtual MIDI service has started, and will
|
||||
return true while the service is running.
|
||||
You can listen for virtualMidiServiceActiveChanged() to take actions (e.g. creating or
|
||||
destroying virtual ports) when the service status changes.
|
||||
*/
|
||||
bool isVirtualMidiUmpServiceActive() const;
|
||||
|
||||
/** By default, Android MIDI services are initially disabled, but can be enabled by calling
|
||||
this function, passing true.
|
||||
This function is *not synchronous*.
|
||||
To find out when the service state changes, listen for virtualMidiServiceActiveChanged()
|
||||
and check the result of isVirtualMidiBytestreamServiceActive().
|
||||
|
||||
On platforms other than Android, this call does nothing.
|
||||
*/
|
||||
void setVirtualMidiBytestreamServiceActive (bool);
|
||||
|
||||
/** By default, Android MIDI services are initially disabled, but can be enabled by calling
|
||||
this function, passing true.
|
||||
This function is *not synchronous*.
|
||||
To find out when the service state changes, listen for virtualMidiServiceActiveChanged()
|
||||
and check the result of isVirtualMidiUmpServiceActive().
|
||||
On Android versions that don't support virtual UMP (API level < 35), this call does nothing.
|
||||
|
||||
On platforms other than Android, this call does nothing.
|
||||
*/
|
||||
void setVirtualMidiUmpServiceActive (bool);
|
||||
|
||||
JUCE_DECLARE_SINGLETON_INLINE (Endpoints, true)
|
||||
|
||||
~Endpoints() override;
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
Endpoints();
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
}
|
||||
126
modules/juce_audio_devices/midi_io/ump/juce_UMPIOHelpers.cpp
Normal file
126
modules/juce_audio_devices/midi_io/ump/juce_UMPIOHelpers.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
struct EndpointAndStaticInfo
|
||||
{
|
||||
Endpoint endpoint;
|
||||
StaticDeviceInfo info;
|
||||
EndpointId id;
|
||||
};
|
||||
|
||||
class IOHelpers
|
||||
{
|
||||
public:
|
||||
IOHelpers() = delete;
|
||||
|
||||
static Block makeLegacyBlock (bool isInput)
|
||||
{
|
||||
const auto direction = isInput ? BlockDirection::receiver : BlockDirection::sender;
|
||||
const auto hint = isInput ? BlockUiHint::receiver : BlockUiHint::sender;
|
||||
return Block{}.withName ("Legacy MIDI 1.0")
|
||||
.withEnabled (true)
|
||||
.withFirstGroup (0)
|
||||
.withNumGroups (1)
|
||||
.withMIDI1ProxyKind (BlockMIDI1ProxyKind::unrestrictedBandwidth)
|
||||
.withDirection (direction)
|
||||
.withUiHint (hint);
|
||||
}
|
||||
|
||||
static EndpointAndStaticInfo makeProxyEndpoint (const MidiDeviceInfo& info, BlockDirection direction)
|
||||
{
|
||||
jassert (direction != BlockDirection::unknown);
|
||||
|
||||
const auto hint = std::invoke ([&]
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case BlockDirection::bidirectional: return BlockUiHint::bidirectional;
|
||||
case BlockDirection::sender: return BlockUiHint::sender;
|
||||
case BlockDirection::receiver: return BlockUiHint::receiver;
|
||||
case BlockDirection::unknown: break;
|
||||
}
|
||||
|
||||
return BlockUiHint::unknown;
|
||||
});
|
||||
|
||||
const auto block = Block{}.withDirection (direction)
|
||||
.withUiHint (hint)
|
||||
.withEnabled (true)
|
||||
.withFirstGroup (0)
|
||||
.withNumGroups (1)
|
||||
.withMIDI1ProxyKind (BlockMIDI1ProxyKind::unrestrictedBandwidth);
|
||||
const auto id = std::invoke ([&]
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case BlockDirection::bidirectional:
|
||||
return EndpointId::makeSrcDst (info.identifier, info.identifier);
|
||||
case BlockDirection::receiver:
|
||||
return EndpointId::make (IOKind::dst, info.identifier);
|
||||
case BlockDirection::sender:
|
||||
return EndpointId::make (IOKind::src, info.identifier);
|
||||
case BlockDirection::unknown: break;
|
||||
}
|
||||
|
||||
return EndpointId{};
|
||||
});
|
||||
|
||||
const auto srcId = direction != BlockDirection::receiver ? info.identifier : "";
|
||||
const auto dstId = direction != BlockDirection::sender ? info.identifier : "";
|
||||
|
||||
std::array<String, 16> srcIds { srcId };
|
||||
std::array<String, 16> dstIds { dstId };
|
||||
|
||||
const auto baseEndpoint = Endpoint{}.withName (info.name)
|
||||
.withProtocol (PacketProtocol::MIDI_1_0)
|
||||
.withUMPVersion (1, 1)
|
||||
.withMidi1Support (true)
|
||||
.withStaticBlocks (true)
|
||||
.withBlocks (std::array { block });
|
||||
|
||||
const auto staticInfo = StaticDeviceInfo{}.withLegacyIdentifiersSrc (srcIds)
|
||||
.withLegacyIdentifiersDst (dstIds)
|
||||
.withHasSource (direction == BlockDirection::sender)
|
||||
.withHasDestination (direction == BlockDirection::receiver)
|
||||
.withName (info.name)
|
||||
.withTransport (Transport::bytestream);
|
||||
|
||||
return { baseEndpoint, staticInfo, id };
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
189
modules/juce_audio_devices/midi_io/ump/juce_UMPInput.cpp
Normal file
189
modules/juce_audio_devices/midi_io/ump/juce_UMPInput.cpp
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
class Input::Impl final : private Consumer,
|
||||
private DisconnectionListener
|
||||
{
|
||||
public:
|
||||
class Native
|
||||
{
|
||||
public:
|
||||
virtual ~Native() = default;
|
||||
|
||||
/* Returns the ID of the endpoint to which this connection is connected. */
|
||||
virtual EndpointId getEndpointId() const = 0;
|
||||
|
||||
/* The protocol to which incoming messages are converted. */
|
||||
virtual PacketProtocol getProtocol() const = 0;
|
||||
};
|
||||
|
||||
EndpointId getEndpointId() const
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
|
||||
PacketProtocol getProtocol() const
|
||||
{
|
||||
return protocol;
|
||||
}
|
||||
|
||||
void addConsumer (Consumer& x)
|
||||
{
|
||||
consumers.add (x);
|
||||
}
|
||||
|
||||
void removeConsumer (Consumer& x)
|
||||
{
|
||||
consumers.remove (x);
|
||||
}
|
||||
|
||||
void addDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
disconnectListeners.add (&x);
|
||||
}
|
||||
|
||||
void removeDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
disconnectListeners.remove (&x);
|
||||
}
|
||||
|
||||
bool isAlive() const
|
||||
{
|
||||
return native != nullptr;
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
static Input makeInput (Callback&& callback)
|
||||
{
|
||||
auto impl = rawToUniquePtr (new Impl);
|
||||
impl->native = callback (static_cast<DisconnectionListener&> (*impl), static_cast<Consumer&> (*impl));
|
||||
|
||||
if (impl->native == nullptr)
|
||||
return {};
|
||||
|
||||
impl->identifier = impl->native->getEndpointId();
|
||||
impl->protocol = impl->native->getProtocol();
|
||||
return Input { std::move (impl) };
|
||||
}
|
||||
|
||||
private:
|
||||
Impl() = default;
|
||||
|
||||
void consume (Iterator b, Iterator e, double t) override
|
||||
{
|
||||
consumers.call ([&] (auto& l) { l.consume (b, e, t); });
|
||||
}
|
||||
|
||||
void disconnected() override
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
native = nullptr;
|
||||
disconnectListeners.call ([] (auto& x) { x.disconnected(); });
|
||||
}
|
||||
|
||||
ListenerList<DisconnectionListener> disconnectListeners;
|
||||
WaitFreeListeners<Consumer> consumers;
|
||||
EndpointId identifier;
|
||||
PacketProtocol protocol;
|
||||
std::unique_ptr<Native> native;
|
||||
};
|
||||
|
||||
Input::Input() = default;
|
||||
Input::~Input() = default;
|
||||
Input::Input (std::unique_ptr<Impl> x) : impl (std::move (x)) {}
|
||||
Input::Input (Input&&) noexcept = default;
|
||||
Input& Input::operator= (Input&&) noexcept = default;
|
||||
|
||||
EndpointId Input::getEndpointId() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getEndpointId();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
PacketProtocol Input::getProtocol() const
|
||||
{
|
||||
// You should ensure that isAlive() returns true before calling other member functions!
|
||||
jassert (isAlive());
|
||||
|
||||
if (impl != nullptr)
|
||||
return impl->getProtocol();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Input::addConsumer (Consumer& x)
|
||||
{
|
||||
// You should ensure that isAlive() returns true before calling other member functions!
|
||||
jassert (isAlive());
|
||||
|
||||
if (impl != nullptr)
|
||||
impl->addConsumer (x);
|
||||
}
|
||||
|
||||
void Input::removeConsumer (Consumer& x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
impl->removeConsumer (x);
|
||||
}
|
||||
|
||||
void Input::addDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
// You should ensure that isAlive() returns true before calling other member functions!
|
||||
jassert (isAlive());
|
||||
|
||||
if (impl != nullptr)
|
||||
impl->addDisconnectionListener (x);
|
||||
}
|
||||
|
||||
void Input::removeDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
impl->removeDisconnectionListener (x);
|
||||
}
|
||||
|
||||
bool Input::isAlive() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->isAlive();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
148
modules/juce_audio_devices/midi_io/ump/juce_UMPInput.h
Normal file
148
modules/juce_audio_devices/midi_io/ump/juce_UMPInput.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An interface class for entities that consume Universal MIDI Packets from some producer.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
struct Consumer
|
||||
{
|
||||
Consumer() = default;
|
||||
|
||||
Consumer (const Consumer&) = default;
|
||||
Consumer (Consumer&&) noexcept = default;
|
||||
|
||||
Consumer& operator= (const Consumer&) = default;
|
||||
Consumer& operator= (Consumer&&) noexcept = default;
|
||||
|
||||
virtual ~Consumer() noexcept = default;
|
||||
|
||||
/** This will be called each time a new packet is ready for processing. */
|
||||
virtual void consume (Iterator b, Iterator e, double time) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An input (from the JUCE project's perspective) that receives messages sent by an endpoint.
|
||||
|
||||
An Input is conceptually similar to a unique_ptr, in that it's a nullable move-only type.
|
||||
You can check the null state of an instance by calling isAlive().
|
||||
isAlive() will return true for an Input that's currently connected, or false otherwise.
|
||||
In particular, isAlive() will return false for a default-constructed Input.
|
||||
If isAlive() returns false, you should avoid calling other member functions:
|
||||
although this won't result in undefined behaviour, none of the functions will produce useful
|
||||
results in this state.
|
||||
|
||||
A particular pitfall to watch out for is calling addConsumer() and removeConsumer() on a
|
||||
default-constructed Input. This has no effect. Instead, if you want to attach listeners to
|
||||
an Input, you should use Session::connectedInput() to create an Input, and ensure that isAlive()
|
||||
returns true on that input before attaching a Consumer.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Input
|
||||
{
|
||||
public:
|
||||
/** Creates a disconnected input.
|
||||
A default-constructed input will never receive any messages.
|
||||
*/
|
||||
Input();
|
||||
~Input();
|
||||
|
||||
Input (Input&&) noexcept;
|
||||
Input& operator= (Input&&) noexcept;
|
||||
|
||||
Input (const Input&) = delete;
|
||||
Input& operator= (const Input&) = delete;
|
||||
|
||||
/** Returns this connection's endpoint. */
|
||||
EndpointId getEndpointId() const;
|
||||
|
||||
/** Returns the protocol that was requested when creating this connection. */
|
||||
PacketProtocol getProtocol() const;
|
||||
|
||||
/** Attaches a receiver that will receive MIDI messages from this input.
|
||||
|
||||
Incoming messages will be converted to the protocol that was requested when
|
||||
opening the Input.
|
||||
|
||||
If isAlive() returns false at the point where this function is called, this function
|
||||
will have no effect. This can commonly happen when attempting to add listeners to a
|
||||
default-constructed Input, or if the input device got disconnected.
|
||||
|
||||
It is an error to add or remove a consumer from within the consumer callback.
|
||||
This will cause deadlocks, so be careful!
|
||||
*/
|
||||
void addConsumer (Consumer& r);
|
||||
|
||||
/** Detaches the receiver so that it will no longer receive MIDI messages from this
|
||||
Input.
|
||||
|
||||
It is an error to add or remove a consumer from within the consumer callback.
|
||||
This will cause deadlocks, so be careful!
|
||||
*/
|
||||
void removeConsumer (Consumer& r);
|
||||
|
||||
/** Attaches a listener that will be notified when this endpoint is disconnected.
|
||||
|
||||
Calling this function on an instance for which isAlive() returns false has no effect.
|
||||
*/
|
||||
void addDisconnectionListener (DisconnectionListener& r);
|
||||
|
||||
/** Removes a disconnection listener. */
|
||||
void removeDisconnectionListener (DisconnectionListener& r);
|
||||
|
||||
/** True if this connection is currently active.
|
||||
|
||||
This function returns false for a default-constructed instance.
|
||||
*/
|
||||
bool isAlive() const;
|
||||
|
||||
explicit operator bool() const { return isAlive(); }
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
explicit Input (std::unique_ptr<Impl>);
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
class LegacyVirtualInput::Impl final
|
||||
{
|
||||
public:
|
||||
class Native
|
||||
{
|
||||
public:
|
||||
virtual ~Native() = default;
|
||||
|
||||
virtual EndpointId getId() const = 0;
|
||||
};
|
||||
|
||||
EndpointId getId() const
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
|
||||
bool isAlive() const
|
||||
{
|
||||
return native != nullptr;
|
||||
}
|
||||
|
||||
static LegacyVirtualInput makeLegacyVirtualInput (std::unique_ptr<Native> x)
|
||||
{
|
||||
if (x != nullptr)
|
||||
return LegacyVirtualInput { rawToUniquePtr (new Impl (std::move (x))) };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Impl (std::unique_ptr<Native> n)
|
||||
: native (std::move (n)),
|
||||
identifier (native->getId())
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<Native> native;
|
||||
EndpointId identifier;
|
||||
};
|
||||
|
||||
LegacyVirtualInput::LegacyVirtualInput() = default;
|
||||
LegacyVirtualInput::~LegacyVirtualInput() = default;
|
||||
LegacyVirtualInput::LegacyVirtualInput (std::unique_ptr<Impl> x) : impl (std::move (x)) {}
|
||||
LegacyVirtualInput::LegacyVirtualInput (LegacyVirtualInput&&) noexcept = default;
|
||||
LegacyVirtualInput& LegacyVirtualInput::operator= (LegacyVirtualInput&&) noexcept = default;
|
||||
|
||||
EndpointId LegacyVirtualInput::getId() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getId();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool LegacyVirtualInput::isAlive() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->isAlive();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
Represents a virtual MIDI 1.0 input port.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class LegacyVirtualInput
|
||||
{
|
||||
public:
|
||||
/** Creates an invalid virtual port that doesn't correspond to any virtual device. */
|
||||
LegacyVirtualInput();
|
||||
~LegacyVirtualInput();
|
||||
|
||||
LegacyVirtualInput (LegacyVirtualInput&&) noexcept;
|
||||
LegacyVirtualInput& operator= (LegacyVirtualInput&&) noexcept;
|
||||
|
||||
LegacyVirtualInput (const LegacyVirtualInput&) = delete;
|
||||
LegacyVirtualInput& operator= (const LegacyVirtualInput&) = delete;
|
||||
|
||||
/** Retrieves the unique id of this input.
|
||||
|
||||
You can pass this ID to Session::createInput in order to receive messages sent to this input.
|
||||
|
||||
Note that this is *not* guaranteed to be stable - creating the 'same' virtual device
|
||||
across several program invocations may produce a different ID each time.
|
||||
|
||||
To fetch the current details of this device, you can pass this ID to Endpoints::getEndpoint().
|
||||
*/
|
||||
EndpointId getId() const;
|
||||
|
||||
bool isAlive() const;
|
||||
|
||||
explicit operator bool() const { return isAlive(); }
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
explicit LegacyVirtualInput (std::unique_ptr<Impl>);
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
class LegacyVirtualOutput::Impl final
|
||||
{
|
||||
public:
|
||||
class Native
|
||||
{
|
||||
public:
|
||||
virtual ~Native() = default;
|
||||
|
||||
virtual EndpointId getId() const = 0;
|
||||
};
|
||||
|
||||
EndpointId getId() const
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
|
||||
bool isAlive() const
|
||||
{
|
||||
return native != nullptr;
|
||||
}
|
||||
|
||||
static LegacyVirtualOutput makeLegacyVirtualOutput (std::unique_ptr<Native> x)
|
||||
{
|
||||
if (x != nullptr)
|
||||
return LegacyVirtualOutput { rawToUniquePtr (new Impl (std::move (x))) };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Impl (std::unique_ptr<Native> n)
|
||||
: native (std::move (n)),
|
||||
identifier (native->getId())
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<Native> native;
|
||||
EndpointId identifier;
|
||||
};
|
||||
|
||||
LegacyVirtualOutput::LegacyVirtualOutput() = default;
|
||||
LegacyVirtualOutput::~LegacyVirtualOutput() = default;
|
||||
LegacyVirtualOutput::LegacyVirtualOutput (std::unique_ptr<Impl> x) : impl (std::move (x)) {}
|
||||
LegacyVirtualOutput::LegacyVirtualOutput (LegacyVirtualOutput&&) noexcept = default;
|
||||
LegacyVirtualOutput& LegacyVirtualOutput::operator= (LegacyVirtualOutput&&) noexcept = default;
|
||||
|
||||
EndpointId LegacyVirtualOutput::getId() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getId();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool LegacyVirtualOutput::isAlive() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->isAlive();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
Represents a virtual MIDI 1.0 output port.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class LegacyVirtualOutput
|
||||
{
|
||||
public:
|
||||
/** Creates an invalid virtual port that doesn't correspond to any virtual device. */
|
||||
LegacyVirtualOutput();
|
||||
~LegacyVirtualOutput();
|
||||
|
||||
LegacyVirtualOutput (LegacyVirtualOutput&&) noexcept;
|
||||
LegacyVirtualOutput& operator= (LegacyVirtualOutput&&) noexcept;
|
||||
|
||||
LegacyVirtualOutput (const LegacyVirtualOutput&) = delete;
|
||||
LegacyVirtualOutput& operator= (const LegacyVirtualOutput&) = delete;
|
||||
|
||||
/** Retrieves the unique id of this input.
|
||||
|
||||
You can pass this ID to Session::createOutput in order to send messages to this output.
|
||||
|
||||
Note that this is *not* guaranteed to be stable - creating the 'same' virtual device
|
||||
across several program invocations may produce a different ID each time.
|
||||
|
||||
To fetch the current details of this device, you can pass this ID to Endpoints::getEndpoint().
|
||||
*/
|
||||
EndpointId getId() const;
|
||||
|
||||
bool isAlive() const;
|
||||
|
||||
explicit operator bool() const { return isAlive(); }
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
explicit LegacyVirtualOutput (std::unique_ptr<Impl>);
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
}
|
||||
159
modules/juce_audio_devices/midi_io/ump/juce_UMPOutput.cpp
Normal file
159
modules/juce_audio_devices/midi_io/ump/juce_UMPOutput.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
class Output::Impl final : private DisconnectionListener
|
||||
{
|
||||
public:
|
||||
class Native
|
||||
{
|
||||
public:
|
||||
virtual ~Native() = default;
|
||||
|
||||
virtual EndpointId getEndpointId() const = 0;
|
||||
|
||||
virtual bool send (Iterator b, Iterator e) = 0;
|
||||
};
|
||||
|
||||
EndpointId getEndpointId() const
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
|
||||
bool send (Iterator beginIterator, Iterator endIterator)
|
||||
{
|
||||
if (native != nullptr)
|
||||
return native->send (beginIterator, endIterator);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void addDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
disconnectListeners.add (&x);
|
||||
}
|
||||
|
||||
void removeDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
disconnectListeners.remove (&x);
|
||||
}
|
||||
|
||||
bool isAlive() const
|
||||
{
|
||||
return native != nullptr;
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
static Output makeOutput (Callback&& callback)
|
||||
{
|
||||
auto impl = rawToUniquePtr (new Impl);
|
||||
impl->native = callback (static_cast<DisconnectionListener&> (*impl));
|
||||
|
||||
if (impl->native == nullptr)
|
||||
return {};
|
||||
|
||||
impl->identifier = impl->native->getEndpointId();
|
||||
return Output { std::move (impl) };
|
||||
}
|
||||
|
||||
private:
|
||||
Impl() = default;
|
||||
|
||||
void disconnected() override
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
native = nullptr;
|
||||
disconnectListeners.call ([] (auto& x) { x.disconnected(); });
|
||||
}
|
||||
|
||||
ListenerList<DisconnectionListener> disconnectListeners;
|
||||
std::unique_ptr<Native> native;
|
||||
EndpointId identifier;
|
||||
};
|
||||
|
||||
Output::Output() = default;
|
||||
Output::~Output() = default;
|
||||
Output::Output (std::unique_ptr<Impl> x) : impl (std::move (x)) {}
|
||||
Output::Output (Output&&) noexcept = default;
|
||||
Output& Output::operator= (Output&&) noexcept = default;
|
||||
|
||||
EndpointId Output::getEndpointId() const
|
||||
{
|
||||
// You should ensure that isAlive() returns true before calling other member functions!
|
||||
jassert (isAlive());
|
||||
|
||||
if (impl != nullptr)
|
||||
return impl->getEndpointId();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Output::send (Iterator beginIterator, Iterator endIterator)
|
||||
{
|
||||
// You should ensure that isAlive() returns true before calling other member functions!
|
||||
jassert (isAlive());
|
||||
|
||||
if (impl != nullptr)
|
||||
return impl->send (beginIterator, endIterator);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Output::addDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
// You should ensure that isAlive() returns true before calling other member functions!
|
||||
jassert (isAlive());
|
||||
|
||||
if (impl != nullptr)
|
||||
impl->addDisconnectionListener (x);
|
||||
}
|
||||
|
||||
void Output::removeDisconnectionListener (DisconnectionListener& x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
impl->removeDisconnectionListener (x);
|
||||
}
|
||||
|
||||
bool Output::isAlive() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->isAlive();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
106
modules/juce_audio_devices/midi_io/ump/juce_UMPOutput.h
Normal file
106
modules/juce_audio_devices/midi_io/ump/juce_UMPOutput.h
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
An output (from the JUCE project's perspective) that sends messages to an endpoint.
|
||||
|
||||
An Output is conceptually similar to a unique_ptr, in that it's a nullable move-only type.
|
||||
You can check the null state of an instance by calling isAlive().
|
||||
isAlive() will return true for an Output that's currently connected, or false otherwise.
|
||||
In particular, isAlive() will return false for a default-constructed Output.
|
||||
If isAlive() returns false, you should avoid calling other member functions:
|
||||
although this won't result in undefined behaviour, none of the functions will produce useful
|
||||
results in this state.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Output
|
||||
{
|
||||
public:
|
||||
/** Creates a disconnected output.
|
||||
Sending messages to a default-constructed output won't do anything.
|
||||
*/
|
||||
Output();
|
||||
~Output();
|
||||
|
||||
Output (Output&&) noexcept;
|
||||
Output& operator= (Output&&) noexcept;
|
||||
|
||||
Output (const Output&) = delete;
|
||||
Output& operator= (const Output&) = delete;
|
||||
|
||||
/** Returns this connection's endpoint. */
|
||||
EndpointId getEndpointId() const;
|
||||
|
||||
/** Sends a range of messages to this endpoint.
|
||||
If isAlive() returns false at the point where this function is called, then this
|
||||
function has no effect.
|
||||
|
||||
Returns true on success, false on failure.
|
||||
|
||||
You may send messages using any protocol and they will be converted automatically
|
||||
to the protocol expected by the receiver.
|
||||
*/
|
||||
bool send (Iterator beginIterator, Iterator endIterator);
|
||||
|
||||
/** Attaches a listener that will be notified when this endpoint is disconnected.
|
||||
|
||||
Calling this function on an instance for which isAlive() returns false has no effect.
|
||||
*/
|
||||
void addDisconnectionListener (DisconnectionListener& r);
|
||||
|
||||
/** Removes a disconnection listener. */
|
||||
void removeDisconnectionListener (DisconnectionListener& r);
|
||||
|
||||
/** True if this connection is currently active.
|
||||
|
||||
This function returns false for a default-constructed instance.
|
||||
*/
|
||||
bool isAlive() const;
|
||||
|
||||
explicit operator bool() const { return isAlive(); }
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
explicit Output (std::unique_ptr<Impl>);
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
}
|
||||
310
modules/juce_audio_devices/midi_io/ump/juce_UMPSession.cpp
Normal file
310
modules/juce_audio_devices/midi_io/ump/juce_UMPSession.cpp
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
class Session::Impl
|
||||
{
|
||||
public:
|
||||
class Native
|
||||
{
|
||||
public:
|
||||
virtual ~Native() = default;
|
||||
|
||||
virtual String getName() const = 0;
|
||||
virtual std::unique_ptr<Input::Impl::Native> connectInput (DisconnectionListener&,
|
||||
const EndpointId&,
|
||||
PacketProtocol,
|
||||
Consumer&) = 0;
|
||||
virtual std::unique_ptr<Output::Impl::Native> connectOutput (DisconnectionListener&,
|
||||
const EndpointId&) = 0;
|
||||
|
||||
/* Creates a full MIDI 2.0 UMP endpoint, or returns nullptr on failure. */
|
||||
virtual std::unique_ptr<VirtualEndpoint::Impl::Native> createNativeVirtualEndpoint (const String&,
|
||||
const DeviceInfo&,
|
||||
const String&,
|
||||
PacketProtocol,
|
||||
Span<const Block>,
|
||||
BlocksAreStatic)
|
||||
{
|
||||
// If this is hit, you're trying to create a virtual (app-to-app) MIDI 2.0 endpoint, but
|
||||
// the current MIDI backend does not implement this feature.
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Creates a virtual MIDI 1.0 port. This is intended for use on platforms that don't
|
||||
support virtual MIDI 2.0.
|
||||
*/
|
||||
virtual std::unique_ptr<LegacyVirtualInput::Impl::Native> createLegacyVirtualInput (const String&)
|
||||
{
|
||||
// If this is hit, you're trying to create a virtual (app-to-app) MIDI 1.0 endpoint, but
|
||||
// the current MIDI backend does not implement this feature.
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Creates a virtual MIDI 1.0 port. This is intended for use on platforms that don't
|
||||
support virtual MIDI 2.0.
|
||||
*/
|
||||
virtual std::unique_ptr<LegacyVirtualOutput::Impl::Native> createLegacyVirtualOutput (const String&)
|
||||
{
|
||||
// If this is hit, you're trying to create a virtual (app-to-app) MIDI 1.0 endpoint, but
|
||||
// the current MIDI backend does not implement this feature.
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
String getName() const
|
||||
{
|
||||
return native->getName();
|
||||
}
|
||||
|
||||
Input makeInput (const EndpointId& endpointId, PacketProtocol protocol)
|
||||
{
|
||||
return Input::Impl::makeInput ([&] (DisconnectionListener& d, Consumer& c)
|
||||
{
|
||||
return native->connectInput (d, endpointId, protocol, c);
|
||||
});
|
||||
}
|
||||
|
||||
Output makeOutput (const EndpointId& endpointId)
|
||||
{
|
||||
return Output::Impl::makeOutput ([&] (DisconnectionListener& d)
|
||||
{
|
||||
return native->connectOutput (d, endpointId);
|
||||
});
|
||||
}
|
||||
|
||||
VirtualEndpoint createVirtualEndpoint (const String& name,
|
||||
const DeviceInfo& info,
|
||||
const String& productInstance,
|
||||
PacketProtocol protocol,
|
||||
Span<const Block> blocks,
|
||||
BlocksAreStatic areStatic)
|
||||
{
|
||||
if (blocks.size() > 32)
|
||||
{
|
||||
// UMP endpoints support a maximum of 32 function blocks
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (name.getNumBytesAsUTF8() > 98)
|
||||
{
|
||||
// Per the spec, there's a length restriction on endpoint names
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto isBlockNameTooLong = [] (const Block& b)
|
||||
{
|
||||
return b.getName().getNumBytesAsUTF8() > 91;
|
||||
};
|
||||
|
||||
if (std::any_of (blocks.begin(), blocks.end(), isBlockNameTooLong))
|
||||
{
|
||||
// Per the spec, there's a length restriction on block names
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto isBlockDisabled = [] (const Block& b) { return ! b.isEnabled(); };
|
||||
|
||||
if (areStatic == BlocksAreStatic::yes && std::any_of (blocks.begin(), blocks.end(), isBlockDisabled))
|
||||
{
|
||||
// You may not request a disabled function block if the function block topology is static
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto umpEndpointNative = native->createNativeVirtualEndpoint (name,
|
||||
info,
|
||||
productInstance,
|
||||
protocol,
|
||||
blocks,
|
||||
areStatic);
|
||||
|
||||
if (umpEndpointNative == nullptr)
|
||||
return {};
|
||||
|
||||
auto result = VirtualEndpoint::Impl::makeVirtualEndpoint (std::move (umpEndpointNative));
|
||||
|
||||
if (! result)
|
||||
return {};
|
||||
|
||||
#if JUCE_ASSERTIONS_ENABLED_OR_LOGGED
|
||||
if (const auto endpoint = Endpoints::getInstance()->getEndpoint (result.getId()))
|
||||
{
|
||||
jassert (endpoint->getName() == name);
|
||||
jassert (endpoint->getProductInstanceId() == productInstance);
|
||||
jassert (endpoint->getProtocol() == protocol);
|
||||
jassert (endpoint->hasStaticBlocks() == (areStatic == BlocksAreStatic::yes));
|
||||
jassert (std::equal (blocks.begin(),
|
||||
blocks.end(),
|
||||
endpoint->getBlocks().begin(),
|
||||
endpoint->getBlocks().end()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unable to find this endpoint, even though we just created it!
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
if (const auto staticInfo = Endpoints::getInstance()->getStaticDeviceInfo (result.getId()))
|
||||
{
|
||||
jassert (staticInfo->getTransport() == Transport::ump);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LegacyVirtualInput createLegacyVirtualInput (const String& name)
|
||||
{
|
||||
if (auto result = native->createLegacyVirtualInput (name))
|
||||
{
|
||||
[[maybe_unused]] const auto id = result->getId();
|
||||
jassert (id.dst.isNotEmpty());
|
||||
return LegacyVirtualInput::Impl::makeLegacyVirtualInput (std::move (result));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
LegacyVirtualOutput createLegacyVirtualOutput (const String& name)
|
||||
{
|
||||
if (auto result = native->createLegacyVirtualOutput (name))
|
||||
{
|
||||
[[maybe_unused]] const auto id = result->getId();
|
||||
jassert (id.src.isNotEmpty());
|
||||
return LegacyVirtualOutput::Impl::makeLegacyVirtualOutput (std::move (result));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Session makeSession (std::unique_ptr<Native> x)
|
||||
{
|
||||
if (x == nullptr)
|
||||
return Session { nullptr };
|
||||
|
||||
return Session { rawToUniquePtr (new Impl (std::move (x))) };
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Impl (std::unique_ptr<Native> n) : native (std::move (n)) {}
|
||||
|
||||
// Order of data members is important to ensure that all inputs+outputs are destroyed before the
|
||||
// native session
|
||||
std::unique_ptr<Native> native;
|
||||
};
|
||||
|
||||
String Session::getName() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getName();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Input Session::connectInput (const EndpointId& x, PacketProtocol p)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->makeInput (x, p);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Output Session::connectOutput (const EndpointId& x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->makeOutput (x);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
VirtualEndpoint Session::createVirtualEndpoint (const String& name,
|
||||
const DeviceInfo& deviceInfo,
|
||||
const String& productInstanceID,
|
||||
PacketProtocol protocol,
|
||||
Span<const Block> initialBlocks,
|
||||
BlocksAreStatic areStatic)
|
||||
{
|
||||
if (impl == nullptr)
|
||||
return {};
|
||||
|
||||
return impl->createVirtualEndpoint (name,
|
||||
deviceInfo,
|
||||
productInstanceID,
|
||||
protocol,
|
||||
initialBlocks,
|
||||
areStatic);
|
||||
}
|
||||
|
||||
LegacyVirtualInput Session::createLegacyVirtualInput (const juce::String& name)
|
||||
{
|
||||
if (impl == nullptr)
|
||||
return {};
|
||||
|
||||
return impl->createLegacyVirtualInput (name);
|
||||
}
|
||||
|
||||
LegacyVirtualOutput Session::createLegacyVirtualOutput (const juce::String& name)
|
||||
{
|
||||
if (impl == nullptr)
|
||||
return {};
|
||||
|
||||
return impl->createLegacyVirtualOutput (name);
|
||||
}
|
||||
|
||||
bool Session::isAlive() const
|
||||
{
|
||||
return impl != nullptr;
|
||||
}
|
||||
|
||||
Session::Session (std::shared_ptr<Impl> x) : impl (std::move (x)) {}
|
||||
Session::Session (const Session&) = default;
|
||||
Session::Session (Session&&) noexcept = default;
|
||||
Session& Session::operator= (const Session&) = default;
|
||||
Session& Session::operator= (Session&&) noexcept = default;
|
||||
Session::~Session() = default;
|
||||
|
||||
}
|
||||
185
modules/juce_audio_devices/midi_io/ump/juce_UMPSession.h
Normal file
185
modules/juce_audio_devices/midi_io/ump/juce_UMPSession.h
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/** This type is passed when creating a virtual endpoint to request static or dynamic blocks. */
|
||||
enum class BlocksAreStatic : uint8_t
|
||||
{
|
||||
no, ///< Indicates that block layouts will not change after construction.
|
||||
yes, ///< Indicates that the block layout may be modified after construction.
|
||||
};
|
||||
|
||||
/**
|
||||
Allows creating new connections to endpoints.
|
||||
|
||||
The session is internally reference counted, meaning that the system resources represented
|
||||
by the session won't be released until the Session object, along with all Inputs, Outputs,
|
||||
and VirtualEndpoints that it created, have been destroyed.
|
||||
|
||||
Internally, sessions cache open connections, so that multiple Inputs
|
||||
or Outputs to the same endpoint will share resources associated with that connection.
|
||||
Therefore, in order to minimise resource usage, you should create as few sessions as possible,
|
||||
and re-use them when creating new connections. It may still make sense to have a separate
|
||||
session for logically discrete components of your project, e.g. a multi-window application
|
||||
where each window has its own document with an associated MIDI configuration.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
public:
|
||||
/** Returns the name that was provided when creating this session, or an empty string
|
||||
if the session is not alive.
|
||||
*/
|
||||
String getName() const;
|
||||
|
||||
/** Creates a connection to a particular endpoint.
|
||||
On failure, returns a disconnected connection (i.e. isAlive() returns false).
|
||||
Passing an EndpointId denoting an endpoint that can only receive messages will fail.
|
||||
|
||||
Incoming messages will be automatically converted to the specified protocol, regardless
|
||||
of their actual protocol 'on the wire'.
|
||||
|
||||
If the session is not alive, this will always fail and return an Input that is not alive.
|
||||
*/
|
||||
Input connectInput (const EndpointId&, PacketProtocol);
|
||||
|
||||
/** Creates a connection to a particular endpoint.
|
||||
On failure, returns a disconnected connection (i.e. isAlive() returns false).
|
||||
Passing an EndpointId denoting an endpoint that can only send messages will fail.
|
||||
|
||||
If the session is not alive, this will always fail and return an Output that is not alive.
|
||||
*/
|
||||
Output connectOutput (const EndpointId&);
|
||||
|
||||
/** Returns a new VirtualEndpoint if virtual endpoints are supported and the configuration is
|
||||
valid. If creating the endpoint fails, this will return an invalid VirtualEndpoint.
|
||||
|
||||
To close the endpoint and remove it from the global MIDI configuration, call
|
||||
destroyVirtualEndpoint().
|
||||
|
||||
To actually send and receive messages through this endpoint, use connectInput
|
||||
and connectOutput to create endpoint connections, in the same way you would treat
|
||||
any other device.
|
||||
|
||||
If the function blocks are static, all blocks must be marked as active.
|
||||
If the function blocks are not static, then blocks may be initially inactive.
|
||||
The number of declared function blocks may not change while the device is active,
|
||||
so if you need a dynamic number of blocks, mark the block layout as non-static
|
||||
and mark any initially unused blocks as inactive.
|
||||
|
||||
Only a maximum of 32 blocks are allowed on an endpoint. If you pass more than 32
|
||||
blocks, this function will fail.
|
||||
|
||||
This function may also fail if virtual devices are not available on the current platform.
|
||||
|
||||
Some platforms (older macOS, older Linux) support virtual MIDI 1.0 devices but not
|
||||
virtual UMP devices. On such platforms, this function will fail. You may wish to check
|
||||
for this case, and to call createLegacyVirtualInput() and/or createLegacyVirtualOutput()
|
||||
as a fall-back.
|
||||
|
||||
If the session is not alive, this will always fail and return a VirtualEndpoint that is not
|
||||
alive.
|
||||
|
||||
On Android, this function will only ever succeed on newer platforms (API level >= 35).
|
||||
You may need to call Endpoints::setVirtualMidiUmpServiceActive() to make the virtual UMP
|
||||
service available. You can listen for state changes in the virtual MIDI service using
|
||||
EndpointsListener::virtualMidiServiceActiveChanged(). A maximum of one VirtualEndpoint may
|
||||
be alive at any time on Android, and attempting to create additional virtual endpoints will
|
||||
fail.
|
||||
*/
|
||||
VirtualEndpoint createVirtualEndpoint (const String& name,
|
||||
const DeviceInfo& deviceInfo,
|
||||
const String& productInstanceID,
|
||||
PacketProtocol protocol,
|
||||
Span<const Block> initialBlocks,
|
||||
BlocksAreStatic);
|
||||
|
||||
/** Creates a MIDI 1.0-compatible input port.
|
||||
|
||||
Where supported by platform APIs, this will explicitly create a single-group MIDI 1.0 port.
|
||||
There are some special cases to keep in mind:
|
||||
|
||||
- Windows MIDI Services only allows creation of UMP endpoints, not MIDI 1.0 ports, so on
|
||||
that platform we create an endpoint with a single MIDI 1.0 Block.
|
||||
- Android requires that virtual ports are declared in the app manifest. JUCE declares a
|
||||
virtual bytestream device with a single input and output, and a virtual UMP device with
|
||||
a single bidirectional port. By default, these virtual devices are disabled, but they
|
||||
can be enabled by calling Endpoints::setVirtualMidiBytestreamServiceActive() and
|
||||
Endpoints::setVirtualMidiUmpServiceActive(). After each service becomes available, you
|
||||
may create exactly one VirtualEndpoint, one LegacyVirtualInput, and/or one
|
||||
LegacyVirtualOutput. Additional virtual ports are not supported.
|
||||
*/
|
||||
LegacyVirtualInput createLegacyVirtualInput (const String& name);
|
||||
|
||||
/** Creates a MIDI 1.0-compatible output port.
|
||||
|
||||
Where supported by platform APIs, this will explicitly create a single-group MIDI 1.0 port.
|
||||
There are some special cases to keep in mind:
|
||||
|
||||
- Windows MIDI Services only allows creation of UMP endpoints, not MIDI 1.0 ports, so on
|
||||
that platform we create an endpoint with a single MIDI 1.0 Block.
|
||||
- Android requires that virtual ports are declared in the app manifest. JUCE declares a
|
||||
virtual bytestream device with a single input and output, and a virtual UMP device with
|
||||
a single bidirectional port. By default, these virtual devices are disabled, but they
|
||||
can be enabled by calling Endpoints::setVirtualMidiBytestreamServiceActive() and
|
||||
Endpoints::setVirtualMidiUmpServiceActive(). After each service becomes available, you
|
||||
may create exactly one VirtualEndpoint, one LegacyVirtualInput, and/or one
|
||||
LegacyVirtualOutput. Additional virtual ports are not supported.
|
||||
*/
|
||||
LegacyVirtualOutput createLegacyVirtualOutput (const String& name);
|
||||
|
||||
/** True if this session was created successfully and is currently alive. */
|
||||
bool isAlive() const;
|
||||
|
||||
explicit operator bool() const { return isAlive(); }
|
||||
|
||||
Session (const Session&);
|
||||
Session (Session&&) noexcept;
|
||||
Session& operator= (const Session&);
|
||||
Session& operator= (Session&&) noexcept;
|
||||
~Session();
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
explicit Session (std::shared_ptr<Impl>);
|
||||
|
||||
std::shared_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
class VirtualEndpoint::Impl final
|
||||
{
|
||||
public:
|
||||
class Native
|
||||
{
|
||||
public:
|
||||
virtual ~Native() = default;
|
||||
|
||||
virtual EndpointId getId() const = 0;
|
||||
virtual bool setBlock (uint8_t, const Block&) = 0;
|
||||
virtual bool setName (const String&) = 0;
|
||||
};
|
||||
|
||||
EndpointId getId() const
|
||||
{
|
||||
return identifier;
|
||||
}
|
||||
|
||||
bool setBlock (uint8_t i, const Block& b)
|
||||
{
|
||||
if (native != nullptr)
|
||||
return native->setBlock (i, b);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool setName (const String& n)
|
||||
{
|
||||
if (native != nullptr)
|
||||
return native->setName (n);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isAlive() const
|
||||
{
|
||||
return native != nullptr;
|
||||
}
|
||||
|
||||
static VirtualEndpoint makeVirtualEndpoint (std::unique_ptr<Native> x)
|
||||
{
|
||||
if (x != nullptr)
|
||||
return VirtualEndpoint { rawToUniquePtr (new Impl (std::move (x))) };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Impl (std::unique_ptr<Native> n)
|
||||
: native (std::move (n)),
|
||||
identifier (native->getId())
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<Native> native;
|
||||
EndpointId identifier;
|
||||
};
|
||||
|
||||
VirtualEndpoint::VirtualEndpoint() = default;
|
||||
VirtualEndpoint::~VirtualEndpoint() = default;
|
||||
VirtualEndpoint::VirtualEndpoint (std::unique_ptr<Impl> x) : impl (std::move (x)) {}
|
||||
VirtualEndpoint::VirtualEndpoint (VirtualEndpoint&&) noexcept = default;
|
||||
VirtualEndpoint& VirtualEndpoint::operator= (VirtualEndpoint&&) noexcept = default;
|
||||
|
||||
EndpointId VirtualEndpoint::getId() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->getId();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool VirtualEndpoint::setBlock (uint8_t i, const Block& b)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->setBlock (i, b);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VirtualEndpoint::setName (const String& x)
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->setName (x);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VirtualEndpoint::isAlive() const
|
||||
{
|
||||
if (impl != nullptr)
|
||||
return impl->isAlive();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
112
modules/juce_audio_devices/midi_io/ump/juce_UMPVirtualEndpoint.h
Normal file
112
modules/juce_audio_devices/midi_io/ump/juce_UMPVirtualEndpoint.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
Represents a virtual device that allows this program to advertise itself to other MIDI-aware
|
||||
applications on the system.
|
||||
|
||||
Creating a VirtualEndpoint will install a new endpoint on the system. This endpoint will
|
||||
be visible when enumerating endpoints using the Endpoints singleton. If you're displaying
|
||||
a list of endpoints in your UI, it's probably a good idea to omit any virtual endpoints
|
||||
created by the current application in order to avoid confusion.
|
||||
|
||||
After creating a VirtualEndpoint, it can be opened like any other connection, by calling
|
||||
Session::connectInput() and Session::connectOutput(), passing the EndpointId for the
|
||||
virtual endpoint.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class VirtualEndpoint
|
||||
{
|
||||
public:
|
||||
/** Creates an invalid virtual endpoint that doesn't correspond to any virtual device. */
|
||||
VirtualEndpoint();
|
||||
~VirtualEndpoint();
|
||||
|
||||
VirtualEndpoint (VirtualEndpoint&&) noexcept;
|
||||
VirtualEndpoint& operator= (VirtualEndpoint&&) noexcept;
|
||||
|
||||
VirtualEndpoint (const VirtualEndpoint&) = delete;
|
||||
VirtualEndpoint& operator= (const VirtualEndpoint&) = delete;
|
||||
|
||||
/** Retrieves the unique id of this endpoint.
|
||||
|
||||
Note that this is *not* guaranteed to be stable - creating the 'same' virtual device
|
||||
across several program invocations may produce a different ID each time.
|
||||
|
||||
To fetch the current details of this device, you can pass this ID to Endpoints::getEndpoint().
|
||||
*/
|
||||
EndpointId getId() const;
|
||||
|
||||
/** Sets new properties for the block at the given zero-based index. The number of function
|
||||
blocks on an endpoint may not change.
|
||||
|
||||
Returns true on success, or false otherwise.
|
||||
|
||||
This may fail for several reasons, including:
|
||||
- attempting to modify an endpoint with static function blocks
|
||||
- attempting to update a block index that doesn't exist on this endpoint
|
||||
- attempting to set a block with invalid properties, e.g. the sum of the start index and
|
||||
number of included groups is greater than 16
|
||||
- platform-specific reasons, e.g. macOS doesn't currently allow changing the number of
|
||||
spanned groups in a block
|
||||
*/
|
||||
bool setBlock (uint8_t index, const Block& newBlock);
|
||||
|
||||
/** Assigns a new name to this endpoint, and sends a notification to connected endpoints. */
|
||||
bool setName (const String&);
|
||||
|
||||
/** Returns true if this object represents an endpoint that is currently alive, or false if
|
||||
the endpoint is not alive. This can happen because the endpoint failed to open, or
|
||||
if the session holding the endpoint was closed.
|
||||
|
||||
This function returns false for a default-constructed instance.
|
||||
*/
|
||||
bool isAlive() const;
|
||||
|
||||
explicit operator bool() const { return isAlive(); }
|
||||
|
||||
/** @internal */
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
explicit VirtualEndpoint (std::unique_ptr<Impl>);
|
||||
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||
import static android.content.pm.PackageManager.DONT_KILL_APP;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.midi.MidiDeviceService;
|
||||
import android.media.midi.MidiReceiver;
|
||||
import android.media.midi.MidiUmpDeviceService;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class VirtualMidiServices
|
||||
{
|
||||
/** Argument is the receiver that should be sent outgoing messages.
|
||||
The return value is a newly-created receiver that will receive incoming messages.
|
||||
*/
|
||||
private static native MidiReceiver addVirtualPort (MidiReceiver output, boolean isUmp);
|
||||
|
||||
/** Argument is a receiver previously returned from addVirtualPort. */
|
||||
private static native void removeVirtualPort (MidiReceiver output);
|
||||
|
||||
public static class VirtualPort extends MidiReceiver
|
||||
{
|
||||
@Override
|
||||
public void onSend (byte[] msg, int offset, int count, long timestamp) throws IOException
|
||||
{
|
||||
if (onSend != null)
|
||||
onSend.send (msg, offset, count, timestamp);
|
||||
}
|
||||
|
||||
public MidiReceiver onSend;
|
||||
}
|
||||
|
||||
public static class VirtualBytestreamService extends MidiDeviceService
|
||||
{
|
||||
@Override
|
||||
public MidiReceiver[] onGetInputPortReceivers()
|
||||
{
|
||||
return new MidiReceiver[] { port };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
MidiReceiver[] receivers = getOutputPortReceivers();
|
||||
port.onSend = addVirtualPort (receivers != null && receivers.length > 0 ? receivers[0] : null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
removeVirtualPort (port.onSend);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private final VirtualPort port = new VirtualPort();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
||||
public static class VirtualUmpService extends MidiUmpDeviceService
|
||||
{
|
||||
@Override
|
||||
public List<MidiReceiver> onGetInputPortReceivers()
|
||||
{
|
||||
return Collections.singletonList (port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
List<MidiReceiver> receivers = getOutputPortReceivers();
|
||||
port.onSend = addVirtualPort (! receivers.isEmpty() ? receivers.getFirst() : null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
removeVirtualPort (port.onSend);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private final VirtualPort port = new VirtualPort();
|
||||
}
|
||||
|
||||
private static void setVirtualMidiServiceEnabled (Context context, Class<?> klass, boolean x)
|
||||
{
|
||||
PackageManager pm = context.getPackageManager();
|
||||
|
||||
if (pm == null)
|
||||
return;
|
||||
|
||||
ComponentName componentName = new ComponentName (context, klass);
|
||||
|
||||
int desiredState = x ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
|
||||
int actualState = pm.getComponentEnabledSetting (componentName);
|
||||
|
||||
if (desiredState == actualState)
|
||||
return;
|
||||
|
||||
pm.setComponentEnabledSetting (componentName, desiredState, DONT_KILL_APP);
|
||||
}
|
||||
|
||||
public static void setVirtualMidiBytestreamEnabled (Context context, boolean x)
|
||||
{
|
||||
setVirtualMidiServiceEnabled (context, VirtualBytestreamService.class, x);
|
||||
}
|
||||
|
||||
public static void setVirtualMidiUmpEnabled (Context context, boolean x)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
||||
setVirtualMidiServiceEnabled (context, VirtualUmpService.class, x);
|
||||
}
|
||||
|
||||
private static boolean isServiceAvailable (Context context, Class k)
|
||||
{
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
Intent intent = new Intent (context, k);
|
||||
List services = packageManager.queryIntentServices (intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
|
||||
return services != null && ! services.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean isVirtualBytestreamAvailable (Context context)
|
||||
{
|
||||
return isServiceAvailable (context, VirtualBytestreamService.class);
|
||||
}
|
||||
|
||||
public static boolean isVirtualUmpAvailable (Context context)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
||||
return false;
|
||||
|
||||
return isServiceAvailable (context, VirtualUmpService.class);
|
||||
}
|
||||
}
|
||||
161
modules/juce_audio_devices/native/juce_ALSA_weak_linux.h
Normal file
161
modules/juce_audio_devices/native/juce_ALSA_weak_linux.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
// This header holds definitions for ALSA symbols that are only present in newer library versions.
|
||||
// It also contains weak definitions for functions that are only present in newer library versions.
|
||||
// By requesting the functions to weak-link, it's possible to build UMP-aware programs on Linux
|
||||
// versions that don't have up-to-date copies of ALSA, and it's also possible to run software
|
||||
// built on older Linux platforms, even if that software was built on a more recent system.
|
||||
|
||||
#if SND_LIB_VERSION < ((1 << 16) | (2 << 8) | 10)
|
||||
|
||||
struct snd_seq_ump_event_t
|
||||
{
|
||||
snd_seq_event_type_t type;
|
||||
unsigned char flags;
|
||||
unsigned char tag;
|
||||
unsigned char queue;
|
||||
snd_seq_timestamp_t time;
|
||||
snd_seq_addr_t source;
|
||||
snd_seq_addr_t dest;
|
||||
|
||||
union
|
||||
{
|
||||
decltype (snd_seq_event_t::data) data; // Older headers don't define snd_seq_event_data_t
|
||||
unsigned int ump[4];
|
||||
};
|
||||
};
|
||||
|
||||
struct snd_ump_endpoint_info_t;
|
||||
struct snd_ump_block_info_t;
|
||||
|
||||
#define snd_ump_block_info_alloca(ptr) __snd_alloca (ptr, snd_ump_block_info)
|
||||
#define snd_ump_endpoint_info_alloca(ptr) __snd_alloca (ptr, snd_ump_endpoint_info)
|
||||
|
||||
constexpr auto SND_SEQ_CLIENT_LEGACY_MIDI = 0;
|
||||
constexpr auto SND_SEQ_CLIENT_UMP_MIDI_1_0 = 1;
|
||||
constexpr auto SND_SEQ_CLIENT_UMP_MIDI_2_0 = 2;
|
||||
|
||||
constexpr auto SND_UMP_EP_INFO_STATIC_BLOCKS = 0x1;
|
||||
constexpr auto SND_UMP_EP_INFO_PROTO_MIDI1 = 0x100;
|
||||
constexpr auto SND_UMP_EP_INFO_PROTO_MIDI2 = 0x200;
|
||||
|
||||
constexpr auto SND_SEQ_PORT_CAP_INACTIVE = 1 << 8;
|
||||
|
||||
constexpr auto SND_SEQ_EVENT_UMP = 1 << 5;
|
||||
|
||||
constexpr auto SND_SEQ_PORT_DIR_INPUT = 1;
|
||||
constexpr auto SND_SEQ_PORT_DIR_OUTPUT = 2;
|
||||
constexpr auto SND_SEQ_PORT_DIR_BIDIRECTION = 3;
|
||||
|
||||
constexpr auto SND_UMP_BLOCK_UI_HINT_UNKNOWN = 0;
|
||||
constexpr auto SND_UMP_BLOCK_UI_HINT_RECEIVER = 1;
|
||||
constexpr auto SND_UMP_BLOCK_UI_HINT_SENDER = 2;
|
||||
constexpr auto SND_UMP_BLOCK_UI_HINT_BOTH = 3;
|
||||
|
||||
constexpr auto SND_UMP_BLOCK_IS_MIDI1 = 1U << 0;
|
||||
constexpr auto SND_UMP_BLOCK_IS_LOWSPEED = 1U << 1;
|
||||
|
||||
constexpr auto SND_UMP_DIR_INPUT = 1;
|
||||
constexpr auto SND_UMP_DIR_OUTPUT = 2;
|
||||
constexpr auto SND_UMP_DIR_BIDIRECTION = 3;
|
||||
|
||||
constexpr auto SND_UMP_EP_INFO_PROTO_JRTS_TX = 1;
|
||||
constexpr auto SND_UMP_EP_INFO_PROTO_JRTS_RX = 2;
|
||||
#endif
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wredundant-decls")
|
||||
|
||||
extern "C"
|
||||
{
|
||||
[[gnu::weak]] int snd_seq_client_info_get_midi_version (const snd_seq_client_info_t*);
|
||||
[[gnu::weak]] int snd_seq_set_client_midi_version (snd_seq_t*, int);
|
||||
[[gnu::weak]] int snd_seq_set_client_ump_conversion (snd_seq_t*, int);
|
||||
[[gnu::weak]] int snd_seq_ump_event_input (snd_seq_t*, snd_seq_ump_event_t**);
|
||||
[[gnu::weak]] int snd_seq_ump_event_output_direct (snd_seq_t*, snd_seq_ump_event_t*);
|
||||
[[gnu::weak]] int snd_seq_port_info_get_ump_is_midi1 (const snd_seq_port_info_t*);
|
||||
[[gnu::weak]] int snd_seq_port_info_get_ump_group (const snd_seq_port_info_t*);
|
||||
[[gnu::weak]] int snd_seq_create_ump_endpoint (snd_seq_t*, const snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] int snd_seq_create_ump_block (snd_seq_t*, int, const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] int snd_seq_set_ump_block_info (snd_seq_t*, int, const void*);
|
||||
[[gnu::weak]] int snd_seq_get_ump_block_info (snd_seq_t*, int, int, void*);
|
||||
[[gnu::weak]] int snd_seq_get_ump_endpoint_info (snd_seq_t*, int, void*);
|
||||
|
||||
[[gnu::weak]] const char* snd_ump_endpoint_info_get_product_id (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] const unsigned char* snd_ump_endpoint_info_get_sw_revision (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_family_id (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_flags (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_manufacturer_id (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_model_id (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_num_blocks (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_protocol (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_protocol_caps (const snd_ump_endpoint_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_endpoint_info_get_version (const snd_ump_endpoint_info_t*);
|
||||
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_product_id (snd_ump_endpoint_info_t*, const char*);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_sw_revision (snd_ump_endpoint_info_t*, const unsigned char*);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_family_id (snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_flags (snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_manufacturer_id (snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_model_id (snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_name (snd_ump_endpoint_info_t*, const char*);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_num_blocks (snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_protocol (snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_protocol_caps (snd_ump_endpoint_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_endpoint_info_set_version (snd_ump_endpoint_info_t*, unsigned int);
|
||||
|
||||
[[gnu::weak]] const char* snd_ump_block_info_get_name (const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_block_info_get_active (const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_block_info_get_direction (const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_block_info_get_first_group (const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_block_info_get_flags (const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_block_info_get_num_groups (const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_block_info_get_sysex8_streams (const snd_ump_block_info_t*);
|
||||
[[gnu::weak]] unsigned int snd_ump_block_info_get_ui_hint (const snd_ump_block_info_t*);
|
||||
|
||||
[[gnu::weak]] void snd_ump_block_info_set_name (snd_ump_block_info_t*, const char*);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_active (snd_ump_block_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_direction (snd_ump_block_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_first_group (snd_ump_block_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_flags (snd_ump_block_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_num_groups (snd_ump_block_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_sysex8_streams (snd_ump_block_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_ui_hint (snd_ump_block_info_t*, unsigned int);
|
||||
[[gnu::weak]] void snd_ump_block_info_set_block_id (snd_ump_block_info_t*, unsigned int);
|
||||
|
||||
[[gnu::weak]] int snd_seq_port_info_get_direction (const snd_seq_port_info_t*);
|
||||
|
||||
[[gnu::weak]] size_t snd_ump_block_info_sizeof (void);
|
||||
[[gnu::weak]] size_t snd_ump_endpoint_info_sizeof (void);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -48,12 +48,6 @@
|
|||
#define JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED 0
|
||||
#endif
|
||||
|
||||
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
|
||||
|
||||
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
||||
#include <juce_audio_basics/midi/ump/juce_UMP.h>
|
||||
#endif
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue