diff --git a/docs/JUCE changelist.txt b/docs/JUCE changelist.txt index 28fcf09b9d..004e3e12f3 100644 --- a/docs/JUCE changelist.txt +++ b/docs/JUCE changelist.txt @@ -6,9 +6,11 @@ ============================================================================== Changelist for version 1.45 -- big new project in the "extras" folder - a basic audio plugin host! Currently it loads VSTs on PC/Mac, and lets you put them together in a filter graph, which it plays. Hosting functionality is very basic at the moment, but I'm laying down a good architecture to hopefully build out into cross-platform plugin loading. -- audio plugins: I've simplified the processBlock() call in AudioFilterBase. It now just takes a single buffer for all input and output channels, and the accumulate parameter has gone. This will mean tweaking your plugin code, but the result will probably make it much less complex and less messy. +- big new project in the "extras" folder - a basic audio plugin host! Currently it loads VSTs on PC/Mac, and lets you put them together in a filter graph, which it plays. Hosting functionality is very basic at the moment, but I'm laying down a good architecture to hopefully develop into a full cross-platform plugin host. +- audio plugins: I've simplified the processBlock() call in AudioFilterBase. It now just takes a single buffer for all input and output channels, and the accumulate parameter has gone. This will mean tweaking your plugin code, but will probably make it much less complicated. - audio plugins: AudioFilterBase now requires a few extra methods to be implemented by your plugin: getInputChannelName, getOutputChannelName, isInputChannelStereoPair, isOutputChannelStereoPair. +- audio plugins: new method AudioFilterBase::updateHostDisplay() to tell the host that something about your plugin has changed and that it should refresh its display. +- audio plugins: new methods AudioFilterBase::beginParameterChangeGesture() and endParameterChangeGesture() let you tell the host when a parameter-change action starts and finishes. - new class: FileSearchPathListComponent, for letting the user edit a FileSearchPath. diff --git a/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj b/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj index 3500b2543b..aabcb709db 100644 --- a/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj +++ b/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj @@ -43,7 +43,7 @@ 20286C33FDCF999611CA2CEA /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; 4A9504C8FFE6A3BC11CA0CBA /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = /System/Library/Frameworks/ApplicationServices.framework; sourceTree = ""; }; 4A9504CAFFE6A41611CA0CBA /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = /System/Library/Frameworks/CoreServices.framework; sourceTree = ""; }; - 508344B209E5C41E0093A071 /* Juce Plugin Host.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Juce Plugin Host.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 508344B209E5C41E0093A071 /* Juce Plugin Host.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "Juce Plugin Host.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 84E5BB950C7203B70088E799 /* juce_GenericAudioFilterEditor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = juce_GenericAudioFilterEditor.cpp; sourceTree = ""; }; 84E5BB960C7203B70088E799 /* juce_GenericAudioFilterEditor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = juce_GenericAudioFilterEditor.h; sourceTree = ""; }; 84FFAE920C6C8A6F009F6E72 /* FilterGraph.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = FilterGraph.cpp; path = ../../src/host/FilterGraph.cpp; sourceTree = SOURCE_ROOT; }; diff --git a/extras/audio plugin host/src/host/FilterGraph.cpp b/extras/audio plugin host/src/host/FilterGraph.cpp index 7f20559a09..5231ca681d 100644 --- a/extras/audio plugin host/src/host/FilterGraph.cpp +++ b/extras/audio plugin host/src/host/FilterGraph.cpp @@ -233,9 +233,11 @@ XmlElement* FilterInGraph::createXml() const return e; } -FilterInGraph* FilterInGraph::createForDescription (FilterGraph& owner, const PluginDescription& desc) +FilterInGraph* FilterInGraph::createForDescription (FilterGraph& owner, + const PluginDescription& desc, + String& errorMessage) { - AudioPluginInstance* instance = desc.createInstance(); + AudioPluginInstance* instance = desc.createInstance (errorMessage); if (instance != 0) return new FilterInGraph (owner, instance); @@ -253,7 +255,8 @@ FilterInGraph* FilterInGraph::createFromXml (FilterGraph& owner, const XmlElemen break; } - FilterInGraph* const c = createForDescription (owner, pd); + String errorMessage; + FilterInGraph* const c = createForDescription (owner, pd, errorMessage); if (c == 0) return 0; @@ -398,13 +401,20 @@ void FilterGraph::addFilter (const PluginDescription* desc, double x, double y) { if (desc != 0) { - FilterInGraph* cf = FilterInGraph::createForDescription (*this, *desc); + String errorMessage; + FilterInGraph* cf = FilterInGraph::createForDescription (*this, *desc, errorMessage); if (cf != 0) { cf->setPosition (x, y); addFilter (cf); } + else + { + AlertWindow::showMessageBox (AlertWindow::WarningIcon, + TRANS("Couldn't create filter"), + errorMessage); + } } } diff --git a/extras/audio plugin host/src/host/FilterGraph.h b/extras/audio plugin host/src/host/FilterGraph.h index 9ee1a80610..00850f5bed 100644 --- a/extras/audio plugin host/src/host/FilterGraph.h +++ b/extras/audio plugin host/src/host/FilterGraph.h @@ -95,7 +95,10 @@ public: XmlElement* createXml() const; - static FilterInGraph* createForDescription (FilterGraph& owner, const PluginDescription& desc); + static FilterInGraph* createForDescription (FilterGraph& owner, + const PluginDescription& desc, + String& errorMessage); + static FilterInGraph* createFromXml (FilterGraph& owner, const XmlElement& xml); //============================================================================== diff --git a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp index 4c7b1a9988..991f8a52fa 100644 --- a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp +++ b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp @@ -67,6 +67,8 @@ void AudioPluginInstance::internalAsyncCallback() changedParamLock.enter(); Array changed; changed.swapWithArray (changedParams); + Array changedValues; + changedValues.swapWithArray (changedParamValues); changedParamLock.exit(); for (int j = 0; j < changed.size(); ++j) @@ -78,9 +80,13 @@ void AudioPluginInstance::internalAsyncCallback() AudioPluginParameterListener* const l = (AudioPluginParameterListener*) listeners.getUnchecked(i); if (paramIndex >= 0) - l->audioPluginParameterChanged (this, paramIndex); - else + l->audioPluginParameterChanged (this, paramIndex, changedValues.getUnchecked(j)); + else if (paramIndex == -1) l->audioPluginChanged (this); + else if ((paramIndex & 0xc0000000) == 0xc0000000) + l->audioPluginParameterChangeGestureBegin (this, paramIndex & 0x3fffffff); + else if ((paramIndex & 0xc0000000) == 0x80000000) + l->audioPluginParameterChangeGestureEnd (this, paramIndex & 0x3fffffff); i = jmin (i, listeners.size()); } @@ -122,20 +128,31 @@ bool JUCE_CALLTYPE AudioPluginInstance::getCurrentPositionInfo (AudioFilterBase: return true; } -void JUCE_CALLTYPE AudioPluginInstance::informHostOfParameterChange (int index, float /*newValue*/) +void JUCE_CALLTYPE AudioPluginInstance::informHostOfParameterChange (int index, float newValue) { - queueChangeMessage (index); + queueChangeMessage (index, newValue); +} + +void JUCE_CALLTYPE AudioPluginInstance::informHostOfParameterGestureBegin (int index) +{ + queueChangeMessage (0xc0000000 | index, 0); +} + +void JUCE_CALLTYPE AudioPluginInstance::informHostOfParameterGestureEnd (int index) +{ + queueChangeMessage (0x80000000 | index, 0); } void JUCE_CALLTYPE AudioPluginInstance::informHostOfStateChange() { - queueChangeMessage (-1); + queueChangeMessage (-1, 0); } -void AudioPluginInstance::queueChangeMessage (const int index) throw() +void AudioPluginInstance::queueChangeMessage (const int index, const float value) throw() { const ScopedLock sl (changedParamLock); - changedParams.addIfNotAlreadyThere (index); + changedParams.add (index); + changedParamValues.add (value); if (! internalAsyncUpdater->isTimerRunning()) internalAsyncUpdater->startTimer (1); @@ -153,3 +170,12 @@ void AudioPluginInstance::InternalAsyncUpdater::timerCallback() owner.internalAsyncCallback(); } + +//============================================================================== +void AudioPluginParameterListener::audioPluginParameterChangeGestureBegin (AudioPluginInstance*, int) +{ +} + +void AudioPluginParameterListener::audioPluginParameterChangeGestureEnd (AudioPluginInstance*, int) +{ +} diff --git a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h index bea6247872..8bf8fab139 100644 --- a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h +++ b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h @@ -47,12 +47,33 @@ public: //============================================================================== /** Receives a callback when a parameter is changed. */ virtual void audioPluginParameterChanged (AudioPluginInstance* plugin, - int parameterIndex) = 0; + int parameterIndex, + float newValue) = 0; /** Called to indicate that something else in the plugin has changed, like its program, number of parameters, etc. */ virtual void audioPluginChanged (AudioPluginInstance* plugin) = 0; + + /** Indicates that a parameter change gesture has started. + + E.g. if the user is dragging a slider, this would be called when they first + press the mouse button, and audioPluginParameterChangeGestureEnd would be + called when they release it. + + @see audioPluginParameterChangeGestureEnd + */ + virtual void audioPluginParameterChangeGestureBegin (AudioPluginInstance* plugin, + int parameterIndex); + + /** Indicates that a parameter change gesture has finished. + + E.g. if the user is dragging a slider, this would be called when they release + the mouse button. + @see audioPluginParameterChangeGestureStart + */ + virtual void audioPluginParameterChangeGestureEnd (AudioPluginInstance* plugin, + int parameterIndex); }; @@ -145,6 +166,7 @@ protected: VoidArray listeners; CriticalSection changedParamLock; Array changedParams; + Array changedParamValues; class InternalAsyncUpdater : public Timer { @@ -162,12 +184,14 @@ protected: InternalAsyncUpdater* internalAsyncUpdater; void internalAsyncCallback(); - void queueChangeMessage (const int index) throw(); + void queueChangeMessage (const int index, const float value) throw(); AudioPluginInstance(); bool JUCE_CALLTYPE getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info); void JUCE_CALLTYPE informHostOfParameterChange (int index, float newValue); + void JUCE_CALLTYPE informHostOfParameterGestureBegin (int index); + void JUCE_CALLTYPE informHostOfParameterGestureEnd (int index); void JUCE_CALLTYPE informHostOfStateChange(); }; diff --git a/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp b/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp index a202a3a78e..f0083c5342 100644 --- a/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp +++ b/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp @@ -64,7 +64,7 @@ public: { } - void audioPluginParameterChanged (AudioPluginInstance*, int parameterIndex) + void audioPluginParameterChanged (AudioPluginInstance*, int parameterIndex, float) { if (parameterIndex == index) refresh(); diff --git a/extras/audio plugin host/src/plugins/juce_PluginDescription.cpp b/extras/audio plugin host/src/plugins/juce_PluginDescription.cpp index 61d9d4c9d5..b292a2260c 100644 --- a/extras/audio plugin host/src/plugins/juce_PluginDescription.cpp +++ b/extras/audio plugin host/src/plugins/juce_PluginDescription.cpp @@ -100,7 +100,7 @@ void PluginDescription::fillInFromInstance (AudioPluginInstance& instance) throw isInstrument = instance.isInstrument(); } -AudioPluginInstance* PluginDescription::createInstance() const +AudioPluginInstance* PluginDescription::createInstance (String& errorMessage) const { AudioPluginInstance* result = 0; @@ -114,6 +114,14 @@ AudioPluginInstance* PluginDescription::createInstance() const break; } + if (result == 0) + { + if (file != File::nonexistent && ! file.exists()) + errorMessage = TRANS ("This plug-in file no longer exists"); + else + errorMessage = TRANS ("This plug-in failed to load correctly"); + } + return result; } diff --git a/extras/audio plugin host/src/plugins/juce_PluginDescription.h b/extras/audio plugin host/src/plugins/juce_PluginDescription.h index f8519c3ed3..7f89cec468 100644 --- a/extras/audio plugin host/src/plugins/juce_PluginDescription.h +++ b/extras/audio plugin host/src/plugins/juce_PluginDescription.h @@ -117,9 +117,10 @@ public: The caller is responsible for deleting the object that is returned. - Returns 0 if it can't load the plugin for some reason. + If it can't load the plugin, it returns 0 and leaves a message in the + errorMessage string. */ - AudioPluginInstance* createInstance() const; + AudioPluginInstance* createInstance (String& errorMessage) const; //============================================================================== /** Creates an XML object containing these details. diff --git a/extras/audio plugin host/src/plugins/juce_PluginListComponent.cpp b/extras/audio plugin host/src/plugins/juce_PluginListComponent.cpp index 7fc0ee176a..55301c774f 100644 --- a/extras/audio plugin host/src/plugins/juce_PluginListComponent.cpp +++ b/extras/audio plugin host/src/plugins/juce_PluginListComponent.cpp @@ -135,6 +135,7 @@ void PluginListComponent::buttonClicked (Button* b) menu.addItem (1, TRANS("Clear list")); menu.addItem (5, TRANS("Remove selected plugin from list"), listBox->getNumSelectedRows() > 0); menu.addItem (6, TRANS("Show folder containing selected plugin"), listBox->getNumSelectedRows() > 0); + menu.addItem (7, TRANS("Remove any plugins whose files no longer exist")); menu.addSeparator(); menu.addItem (2, TRANS("Sort alphabetically")); menu.addItem (3, TRANS("Sort by category")); @@ -182,6 +183,17 @@ void PluginListComponent::buttonClicked (Button* b) if (desc != 0) desc->file.getParentDirectory().startAsProcess(); } + else if (r == 7) + { + for (int i = list.getNumTypes(); --i >= 0;) + { + if (list.getType (i)->file != File::nonexistent + && ! list.getType (i)->file.exists()) + { + list.removeType (i); + } + } + } else if (r != 0) { scanFor (AudioPluginFormatManager::getInstance()->getFormat (r - 10)); diff --git a/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp b/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp index 378f3dea35..f005a8e6a5 100644 --- a/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp @@ -81,6 +81,11 @@ public: Globals()->UseIndexedParameters (juceFilter->getNumParameters()); activePlugins.add (this); + + zerostruct (auEvent); + auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance(); + auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global; + auEvent.mArgument.mParameter.mElement = 0; } ~JuceAU() @@ -219,14 +224,27 @@ public: AudioUnitParameterID inParameterID, AudioUnitParameterInfo& outParameterInfo) { - if (inScope == kAudioUnitScope_Global && juceFilter != 0) + const int index = (int) inParameterID; + + if (inScope == kAudioUnitScope_Global + && juceFilter != 0 + && index < juceFilter->getNumParameters()) { outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable | kAudioUnitParameterFlag_IsReadable | kAudioUnitParameterFlag_HasCFNameString; - outParameterInfo.name[0] = 0; - outParameterInfo.cfNameString = PlatformUtilities::juceStringToCFString (juceFilter->getParameterName ((int) inParameterID)); + const String name (juceFilter->getParameterName (index)); + + CharacterFunctions::copy ((char*) outParameterInfo.name, + (const char*) name.toUTF8(), + sizeof (outParameterInfo.name) - 1); + + // set whether the param is automatable (unnamed parameters aren't allowed to be automated) + if (name.isEmpty() || ! juceFilter->isParameterAutomatable (index)) + outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime; + + outParameterInfo.cfNameString = PlatformUtilities::juceStringToCFString (name); outParameterInfo.minValue = 0.0f; outParameterInfo.maxValue = 1.0f; outParameterInfo.defaultValue = 0.0f; @@ -377,25 +395,37 @@ public: return true; } + void sendAUEvent (const AudioUnitEventType type, const int index) throw() + { + if (AUEventListenerNotify != 0) + { + auEvent.mEventType = type; + auEvent.mArgument.mParameter.mParameterID = (AudioUnitParameterID) index; + AUEventListenerNotify (0, 0, &auEvent); + } + } + void informHostOfParameterChange (int index, float newValue) { if (juceFilter != 0) { juceFilter->setParameter (index, newValue); - - if (AUEventListenerNotify != 0) - { - AudioUnitEvent e; - e.mEventType = kAudioUnitEvent_ParameterValueChange; - e.mArgument.mParameter.mAudioUnit = GetComponentInstance(); - e.mArgument.mParameter.mParameterID = (AudioUnitParameterID) index; - e.mArgument.mParameter.mScope = kAudioUnitScope_Global; - e.mArgument.mParameter.mElement = 0; - AUEventListenerNotify (0, 0, &e); - } + sendAUEvent (kAudioUnitEvent_ParameterValueChange, index); } } + void informHostOfParameterGestureBegin (int index) + { + if (juceFilter != 0) + sendAUEvent (kAudioUnitEvent_BeginParameterChangeGesture, index); + } + + void informHostOfParameterGestureEnd (int index) + { + if (juceFilter != 0) + sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); + } + void informHostOfStateChange() { // xxx is there an AU equivalent? @@ -645,6 +675,7 @@ private: bool prepared; SMPTETime lastSMPTETime; AUChannelInfo channelInfo [numChannelConfigs]; + AudioUnitEvent auEvent; }; diff --git a/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp b/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp index 39bd0a9045..92eaa515ac 100644 --- a/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp @@ -848,6 +848,16 @@ protected: SetControlValue (index + 2, floatToLong (newValue)); } + void JUCE_CALLTYPE informHostOfParameterGestureBegin (int index) + { + TouchControl (index + 2); + } + + void JUCE_CALLTYPE informHostOfParameterGestureEnd (int index) + { + ReleaseControl (index + 2); + } + void JUCE_CALLTYPE informHostOfStateChange() { // xxx is there an RTAS equivalent? diff --git a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp index 4673c284f2..187b4be05e 100644 --- a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp +++ b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp @@ -142,6 +142,13 @@ void AudioFilterStreamer::informHostOfParameterChange (int index, float newValue filter.setParameter (index, newValue); } +void JUCE_CALLTYPE AudioFilterStreamer::informHostOfParameterGestureBegin (int index) +{ +} + +void JUCE_CALLTYPE AudioFilterStreamer::informHostOfParameterGestureEnd (int index) +{ +} void JUCE_CALLTYPE AudioFilterStreamer::informHostOfStateChange() { diff --git a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h index 988a71ec1f..d62a0249b8 100644 --- a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h +++ b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h @@ -73,6 +73,8 @@ public: bool JUCE_CALLTYPE getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info); void JUCE_CALLTYPE informHostOfParameterChange (int index, float newValue); + void JUCE_CALLTYPE informHostOfParameterGestureBegin (int index); + void JUCE_CALLTYPE informHostOfParameterGestureEnd (int index); void JUCE_CALLTYPE informHostOfStateChange(); juce_UseDebuggingNewOperator diff --git a/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp b/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp index 24e6947085..fb710cdf6c 100644 --- a/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp @@ -814,6 +814,16 @@ public: setParameterAutomated (index, newValue); } + void JUCE_CALLTYPE informHostOfParameterGestureBegin (int index) + { + beginEdit (index); + } + + void JUCE_CALLTYPE informHostOfParameterGestureEnd (int index) + { + endEdit (index); + } + void JUCE_CALLTYPE informHostOfStateChange() { updateDisplay(); diff --git a/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp b/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp index c5cae7b753..1fdc87a02a 100644 --- a/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp +++ b/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp @@ -49,9 +49,13 @@ AudioFilterBase::~AudioFilterBase() // ooh, nasty - the editor should have been deleted before the filter // that it refers to is deleted.. jassert (activeEditor == 0); + + // This will fail if you've called beginParameterChangeGesture() for one + // or more parameters without having made a corresponding call to endParameterChangeGesture... + jassert (changingParams.countNumberOfSetBits() == 0); } -void AudioFilterBase::setHostCallbacks (HostCallbacks* const callbacks_) +void AudioFilterBase::setHostCallbacks (HostCallbacks* const callbacks_) throw() { callbacks = callbacks_; } @@ -78,6 +82,31 @@ void AudioFilterBase::setParameterNotifyingHost (const int parameterIndex, setParameter (parameterIndex, newValue); } +void AudioFilterBase::beginParameterChangeGesture (int parameterIndex) +{ + jassert (parameterIndex >= 0 && parameterIndex < getNumParameters()); + + // This means you've called beginParameterChangeGesture twice in succession without a matching + // call to endParameterChangeGesture. That might be fine in most hosts, but better to avoid doing it. + jassert (! changingParams [parameterIndex]); + + if (callbacks != 0) + callbacks->informHostOfParameterGestureBegin (parameterIndex); +} + +void AudioFilterBase::endParameterChangeGesture (int parameterIndex) +{ + jassert (parameterIndex >= 0 && parameterIndex < getNumParameters()); + + // This means you've called endParameterChangeGesture without having previously called + // endParameterChangeGesture. That might be fine in most hosts, but better to keep the + // calls matched correctly. + jassert (changingParams [parameterIndex]); + + if (callbacks != 0) + callbacks->informHostOfParameterGestureEnd (parameterIndex); +} + void JUCE_CALLTYPE AudioFilterBase::updateHostDisplay() { if (callbacks != 0) @@ -103,7 +132,7 @@ bool AudioFilterBase::getCurrentPositionInfo (CurrentPositionInfo& info) } //============================================================================== -void AudioFilterBase::editorBeingDeleted (AudioFilterEditor* const editor) +void AudioFilterBase::editorBeingDeleted (AudioFilterEditor* const editor) throw() { const ScopedLock sl (callbackLock); diff --git a/extras/audio plugins/wrapper/juce_AudioFilterBase.h b/extras/audio plugins/wrapper/juce_AudioFilterBase.h index 806b9cd802..1a6b099633 100644 --- a/extras/audio plugins/wrapper/juce_AudioFilterBase.h +++ b/extras/audio plugins/wrapper/juce_AudioFilterBase.h @@ -403,6 +403,10 @@ public: This could happen when the editor or some other internal operation changes a parameter. This method will call the setParameter() method to change the value, and will then send a message to the host telling it about the change. + + Note that to make sure the host correctly handles automation, you should call + the beginParameterChangeGesture() and endParameterChangeGesture() methods to + tell the host when the user has started and stopped changing the parameter. */ void JUCE_CALLTYPE setParameterNotifyingHost (int parameterIndex, float newValue); @@ -411,7 +415,26 @@ public: By default, this returns true for all parameters. */ - virtual bool isParameterAutomatable (int index) const; + virtual bool isParameterAutomatable (int parameterIndex) const; + + /** Sends a signal to the host to tell it that the user is about to start changing this + parameter. + + This allows the host to know when a parameter is actively being held by the user, and + it may use this information to help it record automation. + + If you call this, it must be matched by a later call to endParameterChangeGesture(). + */ + void beginParameterChangeGesture (int parameterIndex); + + /** Tells the host that the user has finished changing this parameter. + + This allows the host to know when a parameter is actively being held by the user, and + it may use this information to help it record automation. + + A call to this method must follow a call to beginParameterChangeGesture(). + */ + void endParameterChangeGesture (int parameterIndex); /** The filter can call this when something (apart from a parameter value) has changed. @@ -506,6 +529,8 @@ public: virtual bool JUCE_CALLTYPE getCurrentPositionInfo (CurrentPositionInfo& info) = 0; virtual void JUCE_CALLTYPE informHostOfParameterChange (int index, float newValue) = 0; + virtual void JUCE_CALLTYPE informHostOfParameterGestureBegin (int index) = 0; + virtual void JUCE_CALLTYPE informHostOfParameterGestureEnd (int index) = 0; /** Callback to indicate that something (other than a parameter) has changed in the filter, such as its current program, parameter list, etc. */ @@ -517,12 +542,12 @@ public: /** Not for public use - this is called by the wrapper code before deleting an editor component. */ - void JUCE_CALLTYPE editorBeingDeleted (AudioFilterEditor* const editor); + void JUCE_CALLTYPE editorBeingDeleted (AudioFilterEditor* const editor) throw(); /** Not for public use - this is called by the wrapper code to initialise the filter. */ - void JUCE_CALLTYPE setHostCallbacks (HostCallbacks* const); + void JUCE_CALLTYPE setHostCallbacks (HostCallbacks* const) throw(); /** Not for public use - this is called by the wrapper code to initialise the filter. @@ -564,6 +589,10 @@ private: int blockSize, numInputChannels, numOutputChannels; bool suspended; CriticalSection callbackLock; + +#ifdef JUCE_DEBUG + BitArray changingParams; +#endif }; //==============================================================================