mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
AUv3 Client: Use MIDIEventList for output
This commit is contained in:
parent
a6fa345ccc
commit
330792dcee
3 changed files with 189 additions and 123 deletions
|
|
@ -740,7 +740,9 @@ public:
|
||||||
if (inDataSize != sizeof (AUMIDIEventListBlock))
|
if (inDataSize != sizeof (AUMIDIEventListBlock))
|
||||||
return kAudioUnitErr_InvalidPropertyValue;
|
return kAudioUnitErr_InvalidPropertyValue;
|
||||||
|
|
||||||
midiEventListBlock = ScopedMIDIEventListBlock::copy (*static_cast<const AUMIDIEventListBlock*> (inData));
|
if (@available (macos 12, *))
|
||||||
|
eventListOutput.setBlock (*static_cast<const AUMIDIEventListBlock*> (inData));
|
||||||
|
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -2024,53 +2026,8 @@ private:
|
||||||
AUMIDIOutputCallbackStruct midiCallback;
|
AUMIDIOutputCallbackStruct midiCallback;
|
||||||
|
|
||||||
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
||||||
class ScopedMIDIEventListBlock
|
AudioUnitHelpers::EventListOutput eventListOutput;
|
||||||
{
|
|
||||||
public:
|
|
||||||
ScopedMIDIEventListBlock() = default;
|
|
||||||
|
|
||||||
ScopedMIDIEventListBlock (ScopedMIDIEventListBlock&& other) noexcept
|
|
||||||
: midiEventListBlock (std::exchange (other.midiEventListBlock, nil)) {}
|
|
||||||
|
|
||||||
ScopedMIDIEventListBlock& operator= (ScopedMIDIEventListBlock&& other) noexcept
|
|
||||||
{
|
|
||||||
ScopedMIDIEventListBlock { std::move (other) }.swap (*this);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
~ScopedMIDIEventListBlock()
|
|
||||||
{
|
|
||||||
if (midiEventListBlock != nil)
|
|
||||||
[midiEventListBlock release];
|
|
||||||
}
|
|
||||||
|
|
||||||
static ScopedMIDIEventListBlock copy (AUMIDIEventListBlock b)
|
|
||||||
{
|
|
||||||
return ScopedMIDIEventListBlock { b };
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit operator bool() const { return midiEventListBlock != nil; }
|
|
||||||
|
|
||||||
void operator() (AUEventSampleTime eventSampleTime, uint8_t cable, const struct MIDIEventList * eventList) const
|
|
||||||
{
|
|
||||||
jassert (midiEventListBlock != nil);
|
|
||||||
midiEventListBlock (eventSampleTime, cable, eventList);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void swap (ScopedMIDIEventListBlock& other) noexcept
|
|
||||||
{
|
|
||||||
std::swap (other.midiEventListBlock, midiEventListBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit ScopedMIDIEventListBlock (AUMIDIEventListBlock b) : midiEventListBlock ([b copy]) {}
|
|
||||||
|
|
||||||
AUMIDIEventListBlock midiEventListBlock = nil;
|
|
||||||
};
|
|
||||||
|
|
||||||
ScopedMIDIEventListBlock midiEventListBlock;
|
|
||||||
std::optional<SInt32> hostProtocol;
|
std::optional<SInt32> hostProtocol;
|
||||||
ump::ToUMP1Converter toUmp1Converter;
|
|
||||||
ump::ToBytestreamDispatcher toBytestreamDispatcher { 2048 };
|
ump::ToBytestreamDispatcher toBytestreamDispatcher { 2048 };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -2186,61 +2143,8 @@ private:
|
||||||
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
||||||
if (@available (macOS 12.0, iOS 15.0, *))
|
if (@available (macOS 12.0, iOS 15.0, *))
|
||||||
{
|
{
|
||||||
if (midiEventListBlock)
|
if (eventListOutput.trySend (midiEvents, (int64_t) lastTimeStamp.mSampleTime))
|
||||||
{
|
|
||||||
struct MIDIEventList stackList = {};
|
|
||||||
MIDIEventPacket* end = nullptr;
|
|
||||||
|
|
||||||
const auto init = [&]
|
|
||||||
{
|
|
||||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new")
|
|
||||||
end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0);
|
|
||||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto send = [&]
|
|
||||||
{
|
|
||||||
midiEventListBlock (static_cast<int64_t> (lastTimeStamp.mSampleTime), 0, &stackList);
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto add = [&] (const ump::View& view, int timeStamp)
|
|
||||||
{
|
|
||||||
static_assert (sizeof (uint32_t) == sizeof (UInt32)
|
|
||||||
&& alignof (uint32_t) == alignof (UInt32),
|
|
||||||
"If this fails, the cast below will be broken too!");
|
|
||||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new")
|
|
||||||
using List = struct MIDIEventList;
|
|
||||||
end = MIDIEventListAdd (&stackList,
|
|
||||||
sizeof (List::packet),
|
|
||||||
end,
|
|
||||||
(MIDITimeStamp) timeStamp,
|
|
||||||
view.size(),
|
|
||||||
reinterpret_cast<const UInt32*> (view.data()));
|
|
||||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
|
||||||
};
|
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
for (const auto metadata : midiEvents)
|
|
||||||
{
|
|
||||||
toUmp1Converter.convert (ump::BytestreamMidiView (metadata), [&] (const ump::View& view)
|
|
||||||
{
|
|
||||||
add (view, metadata.samplePosition);
|
|
||||||
|
|
||||||
if (end != nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
send();
|
|
||||||
init();
|
|
||||||
add (view, metadata.samplePosition);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
send();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,24 @@
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
|
|
||||||
|
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfour-char-constants")
|
||||||
|
inline constexpr auto pluginIsMidiEffect = JucePlugin_AUMainType == kAudioUnitType_MIDIProcessor;
|
||||||
|
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||||
|
|
||||||
|
inline constexpr auto pluginProducesMidiOutput =
|
||||||
|
#if JucePlugin_ProducesMidiOutput
|
||||||
|
true;
|
||||||
|
#else
|
||||||
|
pluginIsMidiEffect;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline constexpr auto pluginWantsMidiInput =
|
||||||
|
#if JucePlugin_WantsMidiInput
|
||||||
|
true;
|
||||||
|
#else
|
||||||
|
pluginIsMidiEffect;
|
||||||
|
#endif
|
||||||
|
|
||||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness")
|
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness")
|
||||||
|
|
||||||
using namespace juce;
|
using namespace juce;
|
||||||
|
|
@ -384,11 +402,7 @@ public:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
int getVirtualMIDICableCount() const
|
int getVirtualMIDICableCount() const
|
||||||
{
|
{
|
||||||
#if JucePlugin_WantsMidiInput
|
return pluginWantsMidiInput;
|
||||||
return 1;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getSupportsMPE() const
|
bool getSupportsMPE() const
|
||||||
|
|
@ -398,11 +412,10 @@ public:
|
||||||
|
|
||||||
NSArray<NSString*>* getMIDIOutputNames() const
|
NSArray<NSString*>* getMIDIOutputNames() const
|
||||||
{
|
{
|
||||||
#if JucePlugin_ProducesMidiOutput
|
if constexpr (pluginProducesMidiOutput)
|
||||||
return @[@"MIDI Out"];
|
return @[@"MIDI Out"];
|
||||||
#else
|
|
||||||
return @[];
|
return @[];
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -547,6 +560,11 @@ public:
|
||||||
hostMusicalContextCallback = [au musicalContextBlock];
|
hostMusicalContextCallback = [au musicalContextBlock];
|
||||||
hostTransportStateCallback = [au transportStateBlock];
|
hostTransportStateCallback = [au transportStateBlock];
|
||||||
|
|
||||||
|
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
||||||
|
if (@available (macOS 12, iOS 15, *))
|
||||||
|
eventListOutput.setBlock ([au MIDIOutputEventListBlock]);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (@available (macOS 10.13, *))
|
if (@available (macOS 10.13, *))
|
||||||
midiOutputEventBlock = [au MIDIOutputEventBlock];
|
midiOutputEventBlock = [au MIDIOutputEventBlock];
|
||||||
|
|
||||||
|
|
@ -1599,19 +1617,7 @@ private:
|
||||||
// process audio
|
// process audio
|
||||||
processBlock (audioBuffer.getBuffer (frameCount), midiMessages);
|
processBlock (audioBuffer.getBuffer (frameCount), midiMessages);
|
||||||
|
|
||||||
// send MIDI
|
sendMidi ((int64_t) (timestamp->mSampleTime + 0.5), frameCount);
|
||||||
#if JucePlugin_ProducesMidiOutput
|
|
||||||
if (@available (macOS 10.13, *))
|
|
||||||
{
|
|
||||||
if (auto midiOut = midiOutputEventBlock)
|
|
||||||
for (const auto metadata : midiMessages)
|
|
||||||
if (isPositiveAndBelow (metadata.samplePosition, frameCount))
|
|
||||||
midiOut ((int64_t) metadata.samplePosition + (int64_t) (timestamp->mSampleTime + 0.5),
|
|
||||||
0,
|
|
||||||
metadata.numBytes,
|
|
||||||
metadata.data);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy back
|
// copy back
|
||||||
|
|
@ -1621,6 +1627,37 @@ private:
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendMidi (int64_t baseTimeStamp, AUAudioFrameCount frameCount)
|
||||||
|
{
|
||||||
|
if constexpr (pluginProducesMidiOutput)
|
||||||
|
{
|
||||||
|
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
||||||
|
if (@available (macOS 12, iOS 15, *))
|
||||||
|
{
|
||||||
|
if (eventListOutput.trySend (midiMessages, baseTimeStamp))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (@available (macOS 10.13, *))
|
||||||
|
{
|
||||||
|
if (auto midiOut = midiOutputEventBlock)
|
||||||
|
{
|
||||||
|
for (const auto metadata : midiMessages)
|
||||||
|
{
|
||||||
|
if (! isPositiveAndBelow (metadata.samplePosition, frameCount))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
midiOut ((int64_t) metadata.samplePosition + baseTimeStamp,
|
||||||
|
0,
|
||||||
|
metadata.numBytes,
|
||||||
|
metadata.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void processBlock (juce::AudioBuffer<float>& buffer, MidiBuffer& midiBuffer) noexcept
|
void processBlock (juce::AudioBuffer<float>& buffer, MidiBuffer& midiBuffer) noexcept
|
||||||
{
|
{
|
||||||
auto& processor = getAudioProcessor();
|
auto& processor = getAudioProcessor();
|
||||||
|
|
@ -1787,6 +1824,7 @@ private:
|
||||||
AUMIDIOutputEventBlock midiOutputEventBlock = nullptr;
|
AUMIDIOutputEventBlock midiOutputEventBlock = nullptr;
|
||||||
|
|
||||||
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
||||||
|
AudioUnitHelpers::EventListOutput eventListOutput;
|
||||||
ump::ToBytestreamDispatcher converter { 2048 };
|
ump::ToBytestreamDispatcher converter { 2048 };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -590,6 +590,130 @@ struct AudioUnitHelpers
|
||||||
return juceFilter->getBusesLayout();
|
return juceFilter->getBusesLayout();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
|
||||||
|
class ScopedMIDIEventListBlock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScopedMIDIEventListBlock() = default;
|
||||||
|
|
||||||
|
ScopedMIDIEventListBlock (ScopedMIDIEventListBlock&& other) noexcept
|
||||||
|
: midiEventListBlock (std::exchange (other.midiEventListBlock, nil)) {}
|
||||||
|
|
||||||
|
ScopedMIDIEventListBlock& operator= (ScopedMIDIEventListBlock&& other) noexcept
|
||||||
|
{
|
||||||
|
ScopedMIDIEventListBlock { std::move (other) }.swap (*this);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedMIDIEventListBlock()
|
||||||
|
{
|
||||||
|
if (midiEventListBlock != nil)
|
||||||
|
[midiEventListBlock release];
|
||||||
|
}
|
||||||
|
|
||||||
|
static ScopedMIDIEventListBlock copy (AUMIDIEventListBlock b)
|
||||||
|
{
|
||||||
|
return ScopedMIDIEventListBlock { b };
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const { return midiEventListBlock != nil; }
|
||||||
|
|
||||||
|
void operator() (AUEventSampleTime eventSampleTime, uint8_t cable, const struct MIDIEventList * eventList) const
|
||||||
|
{
|
||||||
|
jassert (midiEventListBlock != nil);
|
||||||
|
midiEventListBlock (eventSampleTime, cable, eventList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void swap (ScopedMIDIEventListBlock& other) noexcept
|
||||||
|
{
|
||||||
|
std::swap (other.midiEventListBlock, midiEventListBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit ScopedMIDIEventListBlock (AUMIDIEventListBlock b) : midiEventListBlock ([b copy]) {}
|
||||||
|
|
||||||
|
AUMIDIEventListBlock midiEventListBlock = nil;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EventListOutput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
API_AVAILABLE (macos (12.0), ios (15.0))
|
||||||
|
void setBlock (ScopedMIDIEventListBlock x)
|
||||||
|
{
|
||||||
|
block = std::move (x);
|
||||||
|
}
|
||||||
|
|
||||||
|
API_AVAILABLE (macos (12.0), ios (15.0))
|
||||||
|
void setBlock (AUMIDIEventListBlock x)
|
||||||
|
{
|
||||||
|
setBlock (ScopedMIDIEventListBlock::copy (x));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool trySend (const MidiBuffer& buffer, int64_t baseTimeStamp)
|
||||||
|
{
|
||||||
|
if (! block)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct MIDIEventList stackList = {};
|
||||||
|
MIDIEventPacket* end = nullptr;
|
||||||
|
|
||||||
|
const auto init = [&]
|
||||||
|
{
|
||||||
|
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new")
|
||||||
|
end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0);
|
||||||
|
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto send = [&]
|
||||||
|
{
|
||||||
|
block (baseTimeStamp, 0, &stackList);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto add = [&] (const ump::View& view, int timeStamp)
|
||||||
|
{
|
||||||
|
static_assert (sizeof (uint32_t) == sizeof (UInt32)
|
||||||
|
&& alignof (uint32_t) == alignof (UInt32),
|
||||||
|
"If this fails, the cast below will be broken too!");
|
||||||
|
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new")
|
||||||
|
using List = struct MIDIEventList;
|
||||||
|
end = MIDIEventListAdd (&stackList,
|
||||||
|
sizeof (List::packet),
|
||||||
|
end,
|
||||||
|
(MIDITimeStamp) timeStamp,
|
||||||
|
view.size(),
|
||||||
|
reinterpret_cast<const UInt32*> (view.data()));
|
||||||
|
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
for (const auto metadata : buffer)
|
||||||
|
{
|
||||||
|
toUmp1Converter.convert (ump::BytestreamMidiView (metadata), [&] (const ump::View& view)
|
||||||
|
{
|
||||||
|
add (view, metadata.samplePosition);
|
||||||
|
|
||||||
|
if (end != nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
send();
|
||||||
|
init();
|
||||||
|
add (view, metadata.samplePosition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ScopedMIDIEventListBlock block;
|
||||||
|
ump::ToUMP1Converter toUmp1Converter;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace juce
|
} // namespace juce
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue