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:
parent
a6fa345ccc
commit
330792dcee
3 changed files with 189 additions and 123 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue