diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index 570974762e..260635fad6 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -88,6 +88,7 @@ #include "sources/juce_PositionableAudioSource.cpp" #include "synthesisers/juce_Synthesiser.cpp" #include "audio_play_head/juce_AudioPlayHead.cpp" +#include "midi/juce_MidiDataConcatenator.h" #include "midi/ump/juce_UMP.h" #include "midi/ump/juce_UMPUtils.cpp" diff --git a/modules/juce_audio_basics/midi/ump/juce_UMP.h b/modules/juce_audio_basics/midi/ump/juce_UMP.h index 0fd724ae3f..ea656a653f 100644 --- a/modules/juce_audio_basics/midi/ump/juce_UMP.h +++ b/modules/juce_audio_basics/midi/ump/juce_UMP.h @@ -20,8 +20,6 @@ ============================================================================== */ -#include "../juce_MidiDataConcatenator.h" - #include "juce_UMPProtocols.h" #include "juce_UMPUtils.h" #include "juce_UMPacket.h" diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp index 678354f853..f38b01806c 100644 --- a/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/modules/juce_audio_devices/juce_audio_devices.cpp @@ -50,6 +50,7 @@ //============================================================================== #if JUCE_MAC || JUCE_IOS + #include #include #include "midi_io/ump/juce_UMPBytestreamInputHandler.h" #include "midi_io/ump/juce_UMPU32InputHandler.h" diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 4d63a05966..f19c6881c5 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -388,7 +388,19 @@ public: outWritable = true; return noErr; - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_AudioUnitMIDIProtocol: + outDataSize = sizeof (SInt32); + outWritable = false; + return noErr; + + case kAudioUnitProperty_HostMIDIProtocol: + outDataSize = sizeof (SInt32); + outWritable = true; + return noErr; + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect case kAudioUnitProperty_MIDIOutputCallbackInfo: outDataSize = sizeof (CFArrayRef); outWritable = false; @@ -398,7 +410,14 @@ public: outDataSize = sizeof (AUMIDIOutputCallbackStruct); outWritable = true; return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_MIDIOutputEventListCallback: + outDataSize = sizeof (AUMIDIEventListBlock); + outWritable = true; + return noErr; #endif + #endif case kAudioUnitProperty_ParameterStringFromValue: outDataSize = sizeof (AudioUnitParameterStringFromValue); @@ -542,6 +561,15 @@ public: break; + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_AudioUnitMIDIProtocol: + { + // This will become configurable in the future + *static_cast (outData) = kMIDIProtocol_1_0; + return noErr; + } + #endif + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect case kAudioUnitProperty_MIDIOutputCallbackInfo: { @@ -620,7 +648,7 @@ public: { switch (inID) { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect case kAudioUnitProperty_MIDIOutputCallback: if (inDataSize < sizeof (AUMIDIOutputCallbackStruct)) return kAudioUnitErr_InvalidPropertyValue; @@ -629,6 +657,28 @@ public: midiCallback = *callbackStruct; return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_MIDIOutputEventListCallback: + { + if (inDataSize != sizeof (AUMIDIEventListBlock)) + return kAudioUnitErr_InvalidPropertyValue; + + midiEventListBlock = ScopedMIDIEventListBlock::copy (*static_cast (inData)); + return noErr; + } + #endif + #endif + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_HostMIDIProtocol: + { + if (inDataSize != sizeof (SInt32)) + return kAudioUnitErr_InvalidPropertyValue; + + hostProtocol = *static_cast (inData); + return noErr; + } #endif case kAudioUnitProperty_BypassEffect: @@ -1380,8 +1430,7 @@ public: // process midi output #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - if (! midiEvents.isEmpty() && midiCallback.midiOutputCallback != nullptr) - pushMidiOutput (nFrames); + pushMidiOutput (nFrames); #endif midiEvents.clear(); @@ -1424,6 +1473,30 @@ public: #endif } + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + OSStatus MIDIEventList (UInt32 inOffsetSampleFrame, const struct MIDIEventList* list) override + { + const ScopedLock sl (incomingMidiLock); + + auto* packet = &list->packet[0]; + + for (uint32_t i = 0; i < list->numPackets; ++i) + { + toBytestreamDispatcher.dispatch (reinterpret_cast (packet->words), + reinterpret_cast (packet->words + packet->wordCount), + static_cast (packet->timeStamp + inOffsetSampleFrame), + [this] (const ump::BytestreamMidiView& message) + { + incomingEvents.addEvent (message.getMessage(), (int) message.timestamp); + }); + + packet = MIDIEventPacketNext (packet); + } + + return noErr; + } + #endif + //============================================================================== ComponentResult GetPresets (CFArrayRef* outData) const override { @@ -1846,6 +1919,58 @@ private: mutable Array presetsArray; CriticalSection incomingMidiLock; 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; + std::optional hostProtocol; + ump::ToUMP1Converter toUmp1Converter; + ump::ToBytestreamDispatcher toBytestreamDispatcher { 2048 }; + #endif + AudioTimeStamp lastTimeStamp; int totalInChannels, totalOutChannels; HeapBlock pulledSucceeded; @@ -1952,54 +2077,117 @@ private: void pushMidiOutput ([[maybe_unused]] UInt32 nFrames) noexcept { - MIDIPacket* end = nullptr; + if (midiEvents.isEmpty()) + return; - const auto init = [&] + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + if (@available (macOS 12.0, iOS 15.0, *)) { - end = MIDIPacketListInit (packetList); - }; - - const auto send = [&] - { - midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); - }; - - const auto add = [&] (const MidiMessageMetadata& metadata) - { - end = MIDIPacketListAdd (packetList, - packetListBytes, - end, - static_cast (metadata.samplePosition), - static_cast (metadata.numBytes), - metadata.data); - }; - - init(); - - for (const auto metadata : midiEvents) - { - jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); - - add (metadata); - - if (end == nullptr) + if (midiEventListBlock) { - send(); + struct MIDIEventList stackList = {}; + MIDIEventPacket* end = nullptr; + + const auto init = [&] + { + end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0); + }; + + 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!"); + using List = struct MIDIEventList; + end = MIDIEventListAdd (&stackList, + sizeof (List::packet), + end, + (MIDITimeStamp) timeStamp, + view.size(), + reinterpret_cast (view.data())); + }; + 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; + } + } + #endif + + if (midiCallback.midiOutputCallback) + { + MIDIPacket* end = nullptr; + + const auto init = [&] + { + end = MIDIPacketListInit (packetList); + }; + + const auto send = [&] + { + midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); + }; + + const auto add = [&] (const MidiMessageMetadata& metadata) + { + end = MIDIPacketListAdd (packetList, + packetListBytes, + end, + static_cast (metadata.samplePosition), + static_cast (metadata.numBytes), + metadata.data); + }; + + init(); + + for (const auto metadata : midiEvents) + { + jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); + add (metadata); if (end == nullptr) { - // If this is hit, the size of this midi packet exceeds the maximum size of - // a MIDIPacketList. Large SysEx messages should be broken up into smaller - // chunks. - jassertfalse; + send(); init(); + add (metadata); + + if (end == nullptr) + { + // If this is hit, the size of this midi packet exceeds the maximum size of + // a MIDIPacketList. Large SysEx messages should be broken up into smaller + // chunks. + jassertfalse; + init(); + } } } - } - send(); + send(); + } } void GetAudioBufferList (bool isInput, int busIdx, AudioBufferList*& bufferList, bool& interleaved, int& numChannels) diff --git a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index cab14a473b..555ecefb6d 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -32,11 +32,6 @@ #error AUv3 needs Deployment Target OS X 10.11 or higher to compile #endif -#if (JUCE_IOS && defined (__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) \ - || (JUCE_MAC && defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0) - #define JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED 1 -#endif - #ifndef __OBJC2__ #error AUv3 needs Objective-C 2 support (compile with 64-bit) #endif @@ -56,10 +51,6 @@ #include #include -#if JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED - #include -#endif - #define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3) #if JUCE_IOS @@ -1434,7 +1425,7 @@ private: } break; - #if JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED case AURenderEventMIDIEventList: { const auto& list = event->MIDIEventsList.eventList; @@ -1445,7 +1436,10 @@ private: converter.dispatch (reinterpret_cast (packet->words), reinterpret_cast (packet->words + packet->wordCount), static_cast (packet->timeStamp - (MIDITimeStamp) startTime), - [this] (const MidiMessage& message) { midiMessages.addEvent (message, int (message.getTimeStamp())); }); + [this] (const ump::BytestreamMidiView& message) + { + midiMessages.addEvent (message.getMessage(), (int) message.timestamp); + }); packet = MIDIEventPacketNext (packet); } @@ -1774,7 +1768,7 @@ private: MidiBuffer midiMessages; AUMIDIOutputEventBlock midiOutputEventBlock = nullptr; - #if JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED ump::ToBytestreamDispatcher converter { 2048 }; #endif diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm index ed10cded95..1f81ee0ab1 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm @@ -48,7 +48,9 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wparentheses", "-Wfour-char-constants", "-Wmissing-prototypes", "-Wdeprecated-anon-enum-enum-conversion", - "-Wambiguous-reversed-operator") + "-Wambiguous-reversed-operator", + "-Wunknown-attributes", + "-Wc99-extensions") // From MacOS 10.13 and iOS 11 Apple has (sensibly!) stopped defining a whole // set of functions with rather generic names. However, we still need a couple 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 5ef274d034..036c44b99b 100644 --- a/modules/juce_audio_processors/format_types/juce_AU_Shared.h +++ b/modules/juce_audio_processors/format_types/juce_AU_Shared.h @@ -30,6 +30,20 @@ #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" #endif + +#if (JUCE_IOS && defined (__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) \ + || (JUCE_MAC && defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0) + #define JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED 1 +#else + #define JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED 0 +#endif + +#include + +#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + #include +#endif + namespace juce { diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index ef7b30ad05..0017920ba2 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -44,7 +44,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") #include #include -#include #include "juce_AU_Shared.h" namespace juce