diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm index 7057a0f7f9..809b173cae 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm @@ -740,7 +740,9 @@ public: if (inDataSize != sizeof (AUMIDIEventListBlock)) return kAudioUnitErr_InvalidPropertyValue; - midiEventListBlock = ScopedMIDIEventListBlock::copy (*static_cast (inData)); + if (@available (macos 12, *)) + eventListOutput.setBlock (*static_cast (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 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 (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 (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 diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm index 82b13f4f9f..45a3468da2 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm @@ -69,6 +69,24 @@ #include +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* 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& 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 diff --git a/modules/juce_audio_processors/format_types/juce_AU_Shared.h b/modules/juce_audio_processors/format_types/juce_AU_Shared.h index 1e136f6812..9c7f6cbc48 100644 --- a/modules/juce_audio_processors/format_types/juce_AU_Shared.h +++ b/modules/juce_audio_processors/format_types/juce_AU_Shared.h @@ -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 (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