diff --git a/examples/audio plugin demo/Source/PluginEditor.cpp b/examples/audio plugin demo/Source/PluginEditor.cpp index c9ddc70f65..0cb26334cd 100644 --- a/examples/audio plugin demo/Source/PluginEditor.cpp +++ b/examples/audio plugin demo/Source/PluginEditor.cpp @@ -101,7 +101,7 @@ JuceDemoPluginAudioProcessorEditor::JuceDemoPluginAudioProcessorEditor (JuceDemo timecodeDisplayLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 15.0f, Font::plain)); // set resize limits for this plug-in - setResizeLimits (400, 200, 800, 300); + setResizeLimits (400, 200, 1024, 700); // set our component's initial size to be the last one that was stored in the filter's settings setSize (owner.lastUIWidth, @@ -146,6 +146,11 @@ void JuceDemoPluginAudioProcessorEditor::timerCallback() updateTimecodeDisplay (getProcessor().lastPosInfo); } +void JuceDemoPluginAudioProcessorEditor::hostMIDIControllerIsAvailable (bool controllerIsAvailable) +{ + midiKeyboard.setVisible (! controllerIsAvailable); +} + //============================================================================== // quick-and-dirty function to format a timecode string static String timeToTimecodeString (double seconds) diff --git a/examples/audio plugin demo/Source/PluginEditor.h b/examples/audio plugin demo/Source/PluginEditor.h index eb68c219ac..490875cd6a 100644 --- a/examples/audio plugin demo/Source/PluginEditor.h +++ b/examples/audio plugin demo/Source/PluginEditor.h @@ -44,6 +44,7 @@ public: void paint (Graphics&) override; void resized() override; void timerCallback() override; + void hostMIDIControllerIsAvailable (bool) override; private: class ParameterSlider; 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 2b9f2620df..fe9e6d2e8b 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -37,12 +37,20 @@ #if (! defined MAC_OS_X_VERSION_MIN_REQUIRED) || (! defined MAC_OS_X_VERSION_10_11) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11) #error AUv3 needs Deployment Target OS X 10.11 or higher to compile #endif + #if (defined MAC_OS_X_VERSION_10_13) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13) + #define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 + #define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 + #endif #endif #if JUCE_IOS #if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_9_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0) #error AUv3 needs Deployment Target iOS 9.0 or higher to compile #endif + #if (defined __IPHONE_11_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0) + #define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 + #define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 + #endif #endif #ifndef __OBJC2__ @@ -97,6 +105,15 @@ struct AudioProcessorHolder : public ReferenceCountedObject AudioProcessor* operator->() noexcept { return processor; } AudioProcessor* get() noexcept { return processor; } + struct ViewConfig + { + double width; + double height; + bool hostHasMIDIController; + }; + + ScopedPointer viewConfiguration; + typedef ReferenceCountedObjectPtr Ptr; private: @@ -155,7 +172,7 @@ public: virtual AUParameterTree* getParameterTree() = 0; virtual AUInternalRenderBlock getInternalRenderBlock() = 0; virtual void setRenderingOffline (bool offline) = 0; - virtual NSArray *getChannelCapabilities() = 0; + virtual NSArray* getChannelCapabilities() = 0; //============================================================================== virtual NSArray* parametersForOverviewWithCount (int) @@ -192,6 +209,14 @@ public: ObjCMsgSendSuper (&s, @selector (deallocateRenderResources)); } + virtual bool getSupportsMPE() = 0; + virtual NSArray* getMIDIOutputNames() = 0; + + #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED + virtual NSIndexSet* getSupportedViewConfigurations (NSArray*) = 0; + virtual void selectViewConfiguration (AUAudioUnitViewConfiguration*) = 0; + #endif + private: struct Class : public ObjCClass { @@ -230,13 +255,22 @@ private: addMethod (@selector (parametersForOverviewWithCount:), parametersForOverviewWithCount, "@@:", @encode (NSInteger)); addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @encode (BOOL)); - addMethod (@selector (internalRenderBlock), getInternalRenderBlock, @encode (AUInternalRenderBlock), "@:"); addMethod (@selector (virtualMIDICableCount), getVirtualMIDICableCount, @encode (NSInteger), "@:"); addMethod (@selector (latency), getLatency, @encode (NSTimeInterval), "@:"); addMethod (@selector (tailTime), getTailTime, @encode (NSTimeInterval), "@:"); addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:"); addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:"); + addMethod (@selector (supportsMPE), getSupportsMPE, @encode (BOOL), "@:"); + + #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED + addMethod (@selector (supportedViewConfigurations:), getSupportedViewConfigurations, "@@:@"); + addMethod (@selector (selectViewConfiguration:), selectViewConfiguration, "v@:@"); + #endif + + #if JUCE_AUV3_MIDI_OUTPUT_SUPPORTED + addMethod (@selector (MIDIOutputNames), getMIDIOutputNames, "@@:"); + #endif registerClass(); } @@ -273,28 +307,34 @@ private: return self; } - static void dealloc (id self, SEL) { delete _this (self); } - static AUAudioUnitBusArray* getInputBusses (id self, SEL) { return _this (self)->getInputBusses(); } - static AUAudioUnitBusArray* getOutputBusses (id self, SEL) { return _this (self)->getOutputBusses(); } - static AUParameterTree* getParameterTree (id self, SEL) { return _this (self)->getParameterTree(); } - static AUInternalRenderBlock getInternalRenderBlock (id self, SEL) { return _this (self)->getInternalRenderBlock(); } - static BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; } - static void deallocateRenderResources (id self, SEL) { _this (self)->deallocateRenderResources(); } - static void reset (id self, SEL) { _this (self)->reset(); } - static NSInteger getVirtualMIDICableCount (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); } - static BOOL shouldChangeToFormat (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; } - static NSArray* parametersForOverviewWithCount (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); } - static NSArray* getFactoryPresets (id self, SEL) { return _this (self)->getFactoryPresets(); } - static AUAudioUnitPreset* getCurrentPreset (id self, SEL) { return _this (self)->getCurrentPreset(); } - static void setCurrentPreset (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); } - static NSDictionary* getFullState (id self, SEL) { return _this (self)->getFullState(); } - static void setFullState (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); } - static NSTimeInterval getLatency (id self, SEL) { return _this (self)->getLatency(); } - static NSTimeInterval getTailTime (id self, SEL) { return _this (self)->getTailTime(); } - static BOOL getCanProcessInPlace (id self, SEL) { return _this (self)->getCanProcessInPlace() ? YES : NO; } - static BOOL getRenderingOffline (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; } - static void setRenderingOffline (id self, SEL, BOOL renderingOffline) { _this (self)->setRenderingOffline (renderingOffline); } - static NSArray* getChannelCapabilities (id self, SEL) { return _this (self)->getChannelCapabilities(); } + static void dealloc (id self, SEL) { delete _this (self); } + static AUAudioUnitBusArray* getInputBusses (id self, SEL) { return _this (self)->getInputBusses(); } + static AUAudioUnitBusArray* getOutputBusses (id self, SEL) { return _this (self)->getOutputBusses(); } + static AUParameterTree* getParameterTree (id self, SEL) { return _this (self)->getParameterTree(); } + static AUInternalRenderBlock getInternalRenderBlock (id self, SEL) { return _this (self)->getInternalRenderBlock(); } + static BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; } + static void deallocateRenderResources (id self, SEL) { _this (self)->deallocateRenderResources(); } + static void reset (id self, SEL) { _this (self)->reset(); } + static NSInteger getVirtualMIDICableCount (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); } + static BOOL shouldChangeToFormat (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; } + static NSArray* parametersForOverviewWithCount (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); } + static NSArray* getFactoryPresets (id self, SEL) { return _this (self)->getFactoryPresets(); } + static AUAudioUnitPreset* getCurrentPreset (id self, SEL) { return _this (self)->getCurrentPreset(); } + static void setCurrentPreset (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); } + static NSDictionary* getFullState (id self, SEL) { return _this (self)->getFullState(); } + static void setFullState (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); } + static NSTimeInterval getLatency (id self, SEL) { return _this (self)->getLatency(); } + static NSTimeInterval getTailTime (id self, SEL) { return _this (self)->getTailTime(); } + static BOOL getCanProcessInPlace (id self, SEL) { return _this (self)->getCanProcessInPlace() ? YES : NO; } + static BOOL getRenderingOffline (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; } + static void setRenderingOffline (id self, SEL, BOOL renderingOffline) { _this (self)->setRenderingOffline (renderingOffline); } + static NSArray* getChannelCapabilities (id self, SEL) { return _this (self)->getChannelCapabilities(); } + static BOOL getSupportsMPE (id self, SEL) { return _this (self)->getSupportsMPE() ? YES : NO; } + static NSArray* getMIDIOutputNames (id self, SEL) { return _this (self)->getMIDIOutputNames(); } + #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED + static NSIndexSet* getSupportedViewConfigurations (id self, SEL, NSArray* configs) { return _this (self)->getSupportedViewConfigurations (configs); } + static void selectViewConfiguration (id self, SEL, AUAudioUnitViewConfiguration* config) { _this (self)->selectViewConfiguration (config); } + #endif }; static JuceAudioUnitv3Base* create (AUAudioUnit*, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**); @@ -647,6 +687,60 @@ public: lastTimeStamp.mSampleTime = std::numeric_limits::max(); } + //============================================================================== + #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED + NSIndexSet* getSupportedViewConfigurations (NSArray* configs) override + { + auto supportedViewIndecies = [[NSMutableIndexSet alloc] init]; + auto n = [configs count]; + + if (auto* editor = getAudioProcessor().createEditorIfNeeded()) + { + // If you hit this assertion then your plug-in's editor is reporting that it doesn't support + // any host MIDI controller configurations! + jassert (editor->supportsHostMIDIControllerPresence (true) || editor->supportsHostMIDIControllerPresence (false)); + + for (auto i = 0u; i < n; ++i) + { + if (auto* viewConfiguration = [configs objectAtIndex:i]) + { + if (editor->supportsHostMIDIControllerPresence ([viewConfiguration hostHasController] == YES)) + { + auto* constrainer = editor->getConstrainer(); + auto height = (int) [viewConfiguration height]; + auto width = (int) [viewConfiguration width]; + + if (height <= constrainer->getMaximumHeight() && height >= constrainer->getMinimumHeight() + && width <= constrainer->getMaximumWidth() && width >= constrainer->getMinimumWidth()) + [supportedViewIndecies addIndex: i]; + } + } + } + } + + return [supportedViewIndecies autorelease]; + } + + void selectViewConfiguration (AUAudioUnitViewConfiguration* config) override + { + processorHolder->viewConfiguration = new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }; + } + #endif + + bool getSupportsMPE() override + { + return getAudioProcessor().supportsMPE(); + } + + NSArray* getMIDIOutputNames() override + { + #if JucePlugin_ProducesMidiOutput + return @[@"MIDI Out"]; + #else + return @[]; + #endif + } + //============================================================================== bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) override { @@ -1175,6 +1269,17 @@ private: // process audio processBlock (audioBuffer.getBuffer (frameCount), midiMessages); + + // send MIDI + #if JucePlugin_ProducesMidiOutput && JUCE_AUV3_MIDI_OUTPUT_SUPPORTED + auto midiOut = [au MIDIOutputEventBlock]; + MidiMessage msg; + int samplePosition; + + for (MidiBuffer::Iterator it (midiMessages); it.getNextEvent (msg, samplePosition);) + midiOut (samplePosition, 0, msg.getRawDataSize(), msg.getRawData()); + #endif + midiMessages.clear(); } @@ -1288,8 +1393,8 @@ private: AUParameterObserverToken editorObserverToken; ScopedPointer paramTree; - ScopedPointer > overviewParams; - ScopedPointer > channelCapabilities; + ScopedPointer> overviewParams; + ScopedPointer> channelCapabilities; ScopedPointer > factoryPresets; @@ -1320,7 +1425,6 @@ JuceAudioUnitv3Base* JuceAudioUnitv3Base::create (AUAudioUnit* audioUnit, AudioC class JuceAUViewController { public: - JuceAUViewController (AUViewController* p) : myself (p), processorHolder (nullptr), preferredSize (1.0f, 1.0f) { @@ -1370,6 +1474,9 @@ public: { if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) { + if (processorHolder->viewConfiguration != nullptr) + editor->hostMIDIControllerIsAvailable (processorHolder->viewConfiguration->hostHasMIDIController); + editor->setBounds (convertToRectInt ([[myself view] bounds])); if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp index 295a643605..a061a44665 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -47,7 +47,10 @@ AudioProcessorEditor::~AudioProcessorEditor() } void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} -int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } +int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } + +bool AudioProcessorEditor::supportsHostMIDIControllerPresence (bool) { return true; } +void AudioProcessorEditor::hostMIDIControllerIsAvailable (bool) {} void AudioProcessorEditor::initialise() { diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h index a69f77ee32..e0c0bbbe87 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -85,6 +85,28 @@ public: */ virtual int getControlParameterIndex (Component&); + /** Override this method to indicate if your editor supports the presence or + absence of a host-provided MIDI controller. + + Currently only AUv3 plug-ins compiled for MacOS 10.13 or iOS 11.0 (or later) + support this functionality, and even then the host may choose to ignore this + information. + + The default behaviour is to report support for both cases. + */ + virtual bool supportsHostMIDIControllerPresence (bool hostMIDIControllerIsAvailable); + + /** Called to indicate if a host is providing a MIDI controller when the host + reconfigures its layout. + + Use this as an opportunity to hide or display your own onscreen keyboard or + other input component. + + Currently only AUv3 plug-ins compiled for MacOS 10.13 or iOS 11.0 (or later) + support this functionality. + */ + virtual void hostMIDIControllerIsAvailable (bool controllerIsAvailable); + /** Can be called by a host to tell the editor that it should use a non-unity GUI scale. */