1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

AUv3 Client: Use MIDIEventList for output

This commit is contained in:
reuk 2024-12-04 13:37:54 +00:00
parent a6fa345ccc
commit 330792dcee
3 changed files with 189 additions and 123 deletions

View file

@ -740,7 +740,9 @@ public:
if (inDataSize != sizeof (AUMIDIEventListBlock))
return kAudioUnitErr_InvalidPropertyValue;
midiEventListBlock = ScopedMIDIEventListBlock::copy (*static_cast<const AUMIDIEventListBlock*> (inData));
if (@available (macos 12, *))
eventListOutput.setBlock (*static_cast<const AUMIDIEventListBlock*> (inData));
return noErr;
}
break;
@ -2024,53 +2026,8 @@ private:
AUMIDIOutputCallbackStruct midiCallback;
#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;
};
ScopedMIDIEventListBlock midiEventListBlock;
AudioUnitHelpers::EventListOutput eventListOutput;
std::optional<SInt32> hostProtocol;
ump::ToUMP1Converter toUmp1Converter;
ump::ToBytestreamDispatcher toBytestreamDispatcher { 2048 };
#endif
@ -2186,61 +2143,8 @@ private:
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
if (@available (macOS 12.0, iOS 15.0, *))
{
if (midiEventListBlock)
{
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();
if (eventListOutput.trySend (midiEvents, (int64_t) lastTimeStamp.mSampleTime))
return;
}
}
#endif

View file

@ -69,6 +69,24 @@
#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")
using namespace juce;
@ -384,11 +402,7 @@ public:
//==============================================================================
int getVirtualMIDICableCount() const
{
#if JucePlugin_WantsMidiInput
return 1;
#else
return 0;
#endif
return pluginWantsMidiInput;
}
bool getSupportsMPE() const
@ -398,11 +412,10 @@ public:
NSArray<NSString*>* getMIDIOutputNames() const
{
#if JucePlugin_ProducesMidiOutput
return @[@"MIDI Out"];
#else
if constexpr (pluginProducesMidiOutput)
return @[@"MIDI Out"];
return @[];
#endif
}
//==============================================================================
@ -547,6 +560,11 @@ public:
hostMusicalContextCallback = [au musicalContextBlock];
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, *))
midiOutputEventBlock = [au MIDIOutputEventBlock];
@ -1599,19 +1617,7 @@ private:
// process audio
processBlock (audioBuffer.getBuffer (frameCount), midiMessages);
// send MIDI
#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
sendMidi ((int64_t) (timestamp->mSampleTime + 0.5), frameCount);
}
// copy back
@ -1621,6 +1627,37 @@ private:
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
{
auto& processor = getAudioProcessor();
@ -1787,6 +1824,7 @@ private:
AUMIDIOutputEventBlock midiOutputEventBlock = nullptr;
#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED
AudioUnitHelpers::EventListOutput eventListOutput;
ump::ToBytestreamDispatcher converter { 2048 };
#endif

View file

@ -590,6 +590,130 @@ struct AudioUnitHelpers
return juceFilter->getBusesLayout();
#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