| @@ -107,6 +107,8 @@ JuceDemoPluginAudioProcessorEditor::JuceDemoPluginAudioProcessorEditor (JuceDemo | |||
| setSize (owner.lastUIWidth, | |||
| owner.lastUIHeight); | |||
| updateTrackProperties(); | |||
| // start a timer which will keep our timecode display updated | |||
| startTimerHz (30); | |||
| } | |||
| @@ -118,7 +120,7 @@ JuceDemoPluginAudioProcessorEditor::~JuceDemoPluginAudioProcessorEditor() | |||
| //============================================================================== | |||
| void JuceDemoPluginAudioProcessorEditor::paint (Graphics& g) | |||
| { | |||
| g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); | |||
| g.setColour (backgroundColour); | |||
| g.fillAll(); | |||
| } | |||
| @@ -151,6 +153,16 @@ void JuceDemoPluginAudioProcessorEditor::hostMIDIControllerIsAvailable (bool con | |||
| midiKeyboard.setVisible (! controllerIsAvailable); | |||
| } | |||
| void JuceDemoPluginAudioProcessorEditor::updateTrackProperties () | |||
| { | |||
| auto trackColour = getProcessor().trackProperties.colour; | |||
| auto& lf = getLookAndFeel(); | |||
| backgroundColour = (trackColour == Colour() ? lf.findColour (ResizableWindow::backgroundColourId) | |||
| : trackColour.withAlpha (1.0f).withBrightness (0.266f)); | |||
| repaint(); | |||
| } | |||
| //============================================================================== | |||
| // quick-and-dirty function to format a timecode string | |||
| static String timeToTimecodeString (double seconds) | |||
| @@ -45,6 +45,7 @@ public: | |||
| void resized() override; | |||
| void timerCallback() override; | |||
| void hostMIDIControllerIsAvailable (bool) override; | |||
| void updateTrackProperties(); | |||
| private: | |||
| class ParameterSlider; | |||
| @@ -52,6 +53,7 @@ private: | |||
| MidiKeyboardComponent midiKeyboard; | |||
| Label timecodeDisplayLabel, gainLabel, delayLabel; | |||
| ScopedPointer<ParameterSlider> gainSlider, delaySlider; | |||
| Colour backgroundColour; | |||
| //============================================================================== | |||
| JuceDemoPluginAudioProcessor& getProcessor() const | |||
| @@ -390,6 +390,14 @@ void JuceDemoPluginAudioProcessor::setStateInformation (const void* data, int si | |||
| } | |||
| } | |||
| void JuceDemoPluginAudioProcessor::updateTrackProperties (const TrackProperties& properties) | |||
| { | |||
| trackProperties = properties; | |||
| if (auto* editor = dynamic_cast<JuceDemoPluginAudioProcessorEditor*> (getActiveEditor())) | |||
| editor->updateTrackProperties (); | |||
| } | |||
| //============================================================================== | |||
| // This creates new instances of the plugin.. | |||
| AudioProcessor* JUCE_CALLTYPE createPluginFilter() | |||
| @@ -82,6 +82,9 @@ public: | |||
| void getStateInformation (MemoryBlock&) override; | |||
| void setStateInformation (const void* data, int sizeInBytes) override; | |||
| //============================================================================== | |||
| void updateTrackProperties (const TrackProperties& properties) override; | |||
| //============================================================================== | |||
| // These properties are public so that our editor component can access them | |||
| // A bit of a hacky way to do it, but it's only a demo! Obviously in your own | |||
| @@ -104,6 +107,9 @@ public: | |||
| AudioParameterFloat* gainParam = nullptr; | |||
| AudioParameterFloat* delayParam = nullptr; | |||
| // Current track colour and name | |||
| TrackProperties trackProperties; | |||
| private: | |||
| //============================================================================== | |||
| template <typename FloatType> | |||
| @@ -942,6 +942,13 @@ namespace AAXClasses | |||
| { | |||
| if (type == AAX_eNotificationEvent_EnteringOfflineMode) pluginInstance->setNonRealtime (true); | |||
| if (type == AAX_eNotificationEvent_ExitingOfflineMode) pluginInstance->setNonRealtime (false); | |||
| if (type == AAX_eNotificationEvent_TrackNameChanged && data != nullptr) | |||
| { | |||
| AudioProcessor::TrackProperties props; | |||
| props.name = static_cast<const AAX_IString*> (data)->Get(); | |||
| pluginInstance->updateTrackProperties (props); | |||
| } | |||
| return AAX_CEffectParameters::NotificationReceived (type, data, size); | |||
| } | |||
| @@ -147,6 +147,8 @@ public: | |||
| channelInfo = AudioUnitHelpers::getAUChannelInfo (*juceFilter); | |||
| #endif | |||
| AddPropertyListener (kAudioUnitProperty_ContextName, auPropertyListenerDispatcher, this); | |||
| totalInChannels = juceFilter->getTotalNumInputChannels(); | |||
| totalOutChannels = juceFilter->getTotalNumOutputChannels(); | |||
| @@ -1956,6 +1958,25 @@ private: | |||
| return (getHostType().isLogic() ? 8 : 64); | |||
| } | |||
| //============================================================================== | |||
| void auPropertyListener (AudioUnitPropertyID propId, AudioUnitScope scope, AudioUnitElement) | |||
| { | |||
| if (scope == kAudioUnitScope_Global && propId == kAudioUnitProperty_ContextName | |||
| && juceFilter != nullptr && mContextName != nullptr) | |||
| { | |||
| AudioProcessor::TrackProperties props; | |||
| props.name = String::fromCFString (mContextName); | |||
| juceFilter->updateTrackProperties (props); | |||
| } | |||
| } | |||
| static void auPropertyListenerDispatcher (void* inRefCon, AudioUnit, AudioUnitPropertyID propId, | |||
| AudioUnitScope scope, AudioUnitElement element) | |||
| { | |||
| static_cast<JuceAU*> (inRefCon)->auPropertyListener (propId, scope, element); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (JuceAU) | |||
| }; | |||
| @@ -195,6 +195,10 @@ public: | |||
| virtual bool getRenderingOffline() = 0; | |||
| virtual void setRenderingOffline (bool offline) = 0; | |||
| //============================================================================== | |||
| virtual NSString* getContextName() const = 0; | |||
| virtual void setContextName (NSString*) = 0; | |||
| virtual bool allocateRenderResourcesAndReturnError (NSError **outError) | |||
| { | |||
| objc_super s = { getAudioUnit(), [AUAudioUnit class] }; | |||
| @@ -273,6 +277,10 @@ private: | |||
| addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@"); | |||
| addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); | |||
| //============================================================================== | |||
| addMethod (@selector (contextName), getContextName, "@@:"); | |||
| addMethod (@selector (setContextName:), setContextName, "v@:@"); | |||
| //============================================================================== | |||
| #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED | |||
| addMethod (@selector (supportedViewConfigurations:), getSupportedViewConfigurations, "@@:@"); | |||
| @@ -351,6 +359,10 @@ private: | |||
| 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 NSString* getContextName (id self, SEL) { return _this (self)->getContextName(); } | |||
| static void setContextName (id self, SEL, NSString* str) { return _this (self)->setContextName (str); } | |||
| //============================================================================== | |||
| #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED | |||
| static NSIndexSet* getSupportedViewConfigurations (id self, SEL, NSArray<AUAudioUnitViewConfiguration*>* configs) { return _this (self)->getSupportedViewConfigurations (configs); } | |||
| @@ -679,6 +691,20 @@ public: | |||
| bool getRenderingOffline() override { return getAudioProcessor().isNonRealtime(); } | |||
| void setRenderingOffline (bool offline) override { getAudioProcessor().setNonRealtime (offline); } | |||
| //============================================================================== | |||
| NSString* getContextName() const override { return juceStringToNS (contextName); } | |||
| void setContextName (NSString* str) override | |||
| { | |||
| if (str != nullptr) | |||
| { | |||
| AudioProcessor::TrackProperties props; | |||
| props.name = nsStringToJuce (str); | |||
| getAudioProcessor().updateTrackProperties (props); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| bool allocateRenderResourcesAndReturnError (NSError **outError) override | |||
| { | |||
| AudioProcessor& processor = getAudioProcessor(); | |||
| @@ -1436,6 +1462,8 @@ private: | |||
| AudioTimeStamp lastTimeStamp; | |||
| CurrentPositionInfo lastAudioHead; | |||
| String contextName; | |||
| }; | |||
| const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0; | |||
| @@ -115,6 +115,7 @@ class JuceVST3Component; | |||
| //============================================================================== | |||
| class JuceVST3EditController : public Vst::EditController, | |||
| public Vst::IMidiMapping, | |||
| public Vst::ChannelContext::IInfoListener, | |||
| public AudioProcessorListener | |||
| { | |||
| public: | |||
| @@ -147,6 +148,7 @@ public: | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IEditController2) | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IMidiMapping) | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::ChannelContext::IInfoListener) | |||
| TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IPluginBase, Vst::IEditController) | |||
| TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IDependent, Vst::IEditController) | |||
| TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IEditController) | |||
| @@ -429,6 +431,41 @@ public: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) | |||
| }; | |||
| //============================================================================== | |||
| tresult PLUGIN_API setChannelContextInfos (Vst::IAttributeList* list) override | |||
| { | |||
| if (auto* instance = getPluginInstance()) | |||
| { | |||
| if (list != nullptr) | |||
| { | |||
| AudioProcessor::TrackProperties trackProperties; | |||
| { | |||
| Vst::String128 channelName; | |||
| if (list->getString (Vst::ChannelContext::kChannelNameKey, channelName, sizeof (channelName)) == kResultTrue) | |||
| trackProperties.name = toString (channelName); | |||
| } | |||
| { | |||
| int64 colour; | |||
| if (list->getInt (Vst::ChannelContext::kChannelColorKey, colour) == kResultTrue) | |||
| trackProperties.colour = Colour (Vst::ChannelContext::GetRed ((uint32) colour), Vst::ChannelContext::GetGreen ((uint32) colour), | |||
| Vst::ChannelContext::GetBlue ((uint32) colour), Vst::ChannelContext::GetAlpha ((uint32) colour)); | |||
| } | |||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||
| instance->updateTrackProperties (trackProperties); | |||
| else | |||
| MessageManager::callAsync ([trackProperties, instance] () | |||
| { instance->updateTrackProperties (trackProperties); }); | |||
| } | |||
| } | |||
| return kResultOk; | |||
| } | |||
| //============================================================================== | |||
| tresult PLUGIN_API setComponentState (IBStream* stream) override | |||
| { | |||
| @@ -1120,6 +1157,7 @@ public: | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IAudioProcessor) | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IUnitInfo) | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) | |||
| TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::ChannelContext::IInfoListener) | |||
| TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IComponent) | |||
| if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) | |||
| @@ -1065,6 +1065,19 @@ public: | |||
| jassertfalse; // xxx not implemented! | |||
| } | |||
| //============================================================================== | |||
| void updateTrackProperties (const TrackProperties& properties) override | |||
| { | |||
| if (properties.name.isNotEmpty()) | |||
| { | |||
| CFStringRef contextName = properties.name.toCFString(); | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ContextName, kAudioUnitScope_Global, | |||
| 0, &contextName, sizeof (CFStringRef)); | |||
| CFRelease (contextName); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void getStateInformation (MemoryBlock& destData) override | |||
| { | |||
| @@ -84,6 +84,7 @@ | |||
| #include <pluginterfaces/vst/vsttypes.h> | |||
| #include <pluginterfaces/vst/ivstunits.h> | |||
| #include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
| #include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||
| #include <public.sdk/source/common/memorystream.h> | |||
| #include <public.sdk/source/vst/vsteditcontroller.h> | |||
| #else | |||
| @@ -102,6 +103,7 @@ | |||
| #include <pluginterfaces/gui/iplugview.h> | |||
| #include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | |||
| #include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
| #include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||
| #include <public.sdk/source/common/memorystream.cpp> | |||
| #include <public.sdk/source/common/pluginview.cpp> | |||
| #include <public.sdk/source/vst/vsteditcontroller.cpp> | |||
| @@ -2100,6 +2100,67 @@ struct VST3PluginInstance : public AudioPluginInstance | |||
| return result; | |||
| } | |||
| //============================================================================== | |||
| void updateTrackProperties (const TrackProperties& properties) override | |||
| { | |||
| if (trackInfoListener != nullptr) | |||
| { | |||
| ComSmartPtr<Vst::IAttributeList> l (new TrackPropertiesAttributeList (properties)); | |||
| trackInfoListener->setChannelContextInfos (l); | |||
| } | |||
| } | |||
| struct TrackPropertiesAttributeList : public Vst::IAttributeList | |||
| { | |||
| TrackPropertiesAttributeList (const TrackProperties& properties) : props (properties) {} | |||
| virtual ~TrackPropertiesAttributeList() {} | |||
| JUCE_DECLARE_VST3_COM_REF_METHODS | |||
| tresult PLUGIN_API queryInterface (const TUID iid, void** obj) override | |||
| { | |||
| TEST_FOR_AND_RETURN_IF_VALID (iid, Vst::IAttributeList) | |||
| TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (iid, FUnknown, Vst::IAttributeList) | |||
| *obj = nullptr; | |||
| return kNotImplemented; | |||
| } | |||
| tresult PLUGIN_API setInt (AttrID, int64) override { return kOutOfMemory; } | |||
| tresult PLUGIN_API setFloat (AttrID, double) override { return kOutOfMemory; } | |||
| tresult PLUGIN_API setString (AttrID, const Vst::TChar*) override { return kOutOfMemory; } | |||
| tresult PLUGIN_API setBinary (AttrID, const void*, uint32) override { return kOutOfMemory; } | |||
| tresult PLUGIN_API getFloat (AttrID, double&) override { return kResultFalse; } | |||
| tresult PLUGIN_API getBinary (AttrID, const void*&, uint32&) override { return kResultFalse; } | |||
| tresult PLUGIN_API getString (AttrID id, Vst::TChar* string, uint32 size) override | |||
| { | |||
| if (! std::strcmp (id, Vst::ChannelContext::kChannelNameKey)) | |||
| { | |||
| Steinberg::String str (props.name.toRawUTF8()); | |||
| str.copyTo (string, 0, (Steinberg::int32) jmin (size, (Steinberg::uint32) std::numeric_limits<Steinberg::int32>::max())); | |||
| return kResultTrue; | |||
| } | |||
| return kResultFalse; | |||
| } | |||
| tresult PLUGIN_API getInt (AttrID id, int64& value) override | |||
| { | |||
| if (! std::strcmp (Vst::ChannelContext::kChannelNameLengthKey, id)) value = props.name.length(); | |||
| else if (! std::strcmp (Vst::ChannelContext::kChannelColorKey, id)) value = static_cast<int64> (props.colour.getARGB()); | |||
| else return kResultFalse; | |||
| return kResultTrue; | |||
| } | |||
| Atomic<int> refCount; | |||
| TrackProperties props; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrackPropertiesAttributeList) | |||
| }; | |||
| //============================================================================== | |||
| String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const | |||
| { | |||
| @@ -2449,6 +2510,7 @@ private: | |||
| ComSmartPtr<Vst::IUnitData> unitData; | |||
| ComSmartPtr<Vst::IProgramListData> programListData; | |||
| ComSmartPtr<Vst::IConnectionPoint> componentConnection, editControllerConnection; | |||
| ComSmartPtr<Vst::ChannelContext::IInfoListener> trackInfoListener; | |||
| /** The number of IO buses MUST match that of the plugin, | |||
| even if there aren't enough channels to process, | |||
| @@ -2533,6 +2595,7 @@ private: | |||
| editController2.loadFrom (holder->component); | |||
| componentHandler.loadFrom (holder->component); | |||
| componentHandler2.loadFrom (holder->component); | |||
| trackInfoListener.loadFrom (holder->component); | |||
| if (processor == nullptr) processor.loadFrom (editController); | |||
| if (unitInfo == nullptr) unitInfo.loadFrom (editController); | |||
| @@ -2541,6 +2604,7 @@ private: | |||
| if (editController2 == nullptr) editController2.loadFrom (editController); | |||
| if (componentHandler == nullptr) componentHandler.loadFrom (editController); | |||
| if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController); | |||
| if (trackInfoListener == nullptr) trackInfoListener.loadFrom (editController); | |||
| } | |||
| void setStateForAllMidiBuses (bool newState) | |||
| @@ -1025,6 +1025,9 @@ void AudioProcessor::setCurrentProgramStateInformation (const void* data, int si | |||
| setStateInformation (data, sizeInBytes); | |||
| } | |||
| //============================================================================== | |||
| void AudioProcessor::updateTrackProperties (const AudioProcessor::TrackProperties&) {} | |||
| //============================================================================== | |||
| // magic number to identify memory blocks that we've stored as XML | |||
| const uint32 magicXmlNumber = 0x21324356; | |||
| @@ -1310,6 +1310,32 @@ public: | |||
| */ | |||
| WrapperType wrapperType; | |||
| /** A struct containing information about the DAW track inside which your | |||
| AudioProcessor is loaded. */ | |||
| struct TrackProperties | |||
| { | |||
| String name; // The name of the track - this will be empty if the track name is not known | |||
| Colour colour; // The colour of the track - this will be transparentBlack if the colour is not known | |||
| // other properties may be added in the future | |||
| }; | |||
| /** Informs the AudioProcessor that track properties such as the track's name or | |||
| colour has been changed. | |||
| If you are hosting this AudioProcessor then use this method to inform the | |||
| AudioProcessor about which track the AudioProcessor is loaded on. This method | |||
| may only be called on the message thread. | |||
| If you are implemeting an AudioProcessor then you can override this callback | |||
| to do something useful with the track properties such as changing the colour | |||
| of your AudioProcessor's editor. It's entirely up to the host when and how | |||
| often this callback will be called. | |||
| The default implementation of this callback will do nothing. | |||
| */ | |||
| virtual void updateTrackProperties (const TrackProperties& properties); | |||
| //============================================================================== | |||
| #ifndef DOXYGEN | |||
| /** Deprecated: use getTotalNumInputChannels instead. */ | |||