diff --git a/extras/audio plugin host/src/host/FilterGraph.cpp b/extras/audio plugin host/src/host/FilterGraph.cpp index a91e9f61f1..7f20559a09 100644 --- a/extras/audio plugin host/src/host/FilterGraph.cpp +++ b/extras/audio plugin host/src/host/FilterGraph.cpp @@ -74,6 +74,11 @@ public: ~PluginWindow() { + if (owner.activeGenericUI == this) + owner.activeGenericUI = 0; + else + owner.activeUI = 0; + setContentComponent (0); } @@ -85,7 +90,6 @@ public: void closeButtonPressed() { - owner.activeUI = 0; delete this; } @@ -100,7 +104,8 @@ FilterInGraph::FilterInGraph (FilterGraph& owner_, AudioPluginInstance* const fi filter (filter_), uid (0), processedAudio (1, 1), - activeUI (0) + activeUI (0), + activeGenericUI (0) { lastX = 100 + Random::getSystemRandom().nextInt (400); lastY = 100 + Random::getSystemRandom().nextInt (400); @@ -108,6 +113,7 @@ FilterInGraph::FilterInGraph (FilterGraph& owner_, AudioPluginInstance* const fi FilterInGraph::~FilterInGraph() { + delete activeGenericUI; delete activeUI; delete filter; } @@ -118,21 +124,37 @@ void FilterInGraph::setPosition (double newX, double newY) throw() y = jlimit (0.0, 1.0, newY); } -void FilterInGraph::showUI() +void FilterInGraph::showUI (bool useGenericUI) { - if (activeUI == 0) + if (! useGenericUI) { - Component* ui = filter->createEditorIfNeeded(); + if (activeUI == 0) + { + Component* ui = filter->createEditorIfNeeded(); - if (ui == 0) - ui = new GenericAudioFilterEditor (filter); + if (ui == 0) + return showUI (true); - ui->setName (filter->getName()); - activeUI = new PluginWindow (ui, *this); + ui->setName (filter->getName()); + activeUI = new PluginWindow (ui, *this); + } + + if (activeUI != 0) + activeUI->toFront (true); } + else + { + if (activeGenericUI == 0) + { + Component* ui = new GenericAudioFilterEditor (filter); + + ui->setName (filter->getName()); + activeGenericUI = new PluginWindow (ui, *this); + } - if (activeUI != 0) - activeUI->toFront (true); + if (activeGenericUI != 0) + activeGenericUI->toFront (true); + } } void FilterInGraph::prepareBuffers (int blockSize) diff --git a/extras/audio plugin host/src/host/FilterGraph.h b/extras/audio plugin host/src/host/FilterGraph.h index c732f8cfe8..9ee1a80610 100644 --- a/extras/audio plugin host/src/host/FilterGraph.h +++ b/extras/audio plugin host/src/host/FilterGraph.h @@ -87,7 +87,7 @@ public: uint32 uid; //============================================================================== - void showUI(); + void showUI (bool useGenericUI); double getX() const throw() { return x; } double getY() const throw() { return y; } @@ -111,6 +111,7 @@ private: friend class PluginWindow; Component* activeUI; + Component* activeGenericUI; int lastX, lastY; MidiBuffer outputMidi; diff --git a/extras/audio plugin host/src/host/GraphEditorPanel.cpp b/extras/audio plugin host/src/host/GraphEditorPanel.cpp index 210fc97e60..31a5e8dc28 100644 --- a/extras/audio plugin host/src/host/GraphEditorPanel.cpp +++ b/extras/audio plugin host/src/host/GraphEditorPanel.cpp @@ -145,6 +145,9 @@ public: PopupMenu m; m.addItem (1, "Delete this filter"); m.addItem (2, "Disconnect all pins"); + m.addSeparator(); + m.addItem (3, "Show plugin UI"); + m.addItem (4, "Show all parameters"); const int r = m.show(); @@ -157,6 +160,13 @@ public: { graph.disconnectFilter (filterID); } + else if (r == 3 || r == 4) + { + const FilterInGraph::Ptr f (graph.getFilterForUID (filterID)); + + if (f != 0) + f->showUI (r == 4); + } } } @@ -189,7 +199,7 @@ public: const FilterInGraph::Ptr f (graph.getFilterForUID (filterID)); if (f != 0) - f->showUI(); + f->showUI (false); } else if (! e.mouseWasClicked()) { diff --git a/extras/audio plugin host/src/host/MainHostWindow.cpp b/extras/audio plugin host/src/host/MainHostWindow.cpp index 892676de3c..8abfb47f98 100644 --- a/extras/audio plugin host/src/host/MainHostWindow.cpp +++ b/extras/audio plugin host/src/host/MainHostWindow.cpp @@ -127,6 +127,8 @@ MainHostWindow::MainHostWindow() knownPluginList.addChangeListener (this); addKeyListener (commandManager->getKeyMappings()); + + Process::setPriority (Process::HighPriority); } MainHostWindow::~MainHostWindow() diff --git a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp index ed89e1ecba..82c8e99750 100644 --- a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp +++ b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.cpp @@ -36,10 +36,56 @@ #include "../../../audio plugins/wrapper/juce_AudioFilterBase.cpp" #include "../../../audio plugins/wrapper/juce_AudioFilterEditor.cpp" +#include "juce_AudioPluginInstance.h" + //============================================================================== -#include "juce_AudioPluginInstance.h" +AudioPluginInstance::AudioPluginInstance() +{ + internalAsyncUpdater = new InternalAsyncUpdater (*this); + + initialiseInternal (this); +} + +AudioPluginInstance::~AudioPluginInstance() +{ + delete internalAsyncUpdater; +} + +void AudioPluginInstance::addListener (AudioPluginParameterListener* const newListener) throw() +{ + listeners.addIfNotAlreadyThere (newListener); +} +void AudioPluginInstance::removeListener (AudioPluginParameterListener* const listenerToRemove) throw() +{ + listeners.removeValue (listenerToRemove); +} + +void AudioPluginInstance::internalAsyncCallback() +{ + changedParamLock.enter(); + Array changed; + changed.swapWithArray (changedParams); + changedParamLock.exit(); + + for (int j = 0; j < changed.size(); ++j) + { + const int paramIndex = changed.getUnchecked (j); + + for (int i = listeners.size(); --i >= 0;) + { + AudioPluginParameterListener* const l = (AudioPluginParameterListener*) listeners.getUnchecked(i); + + if (paramIndex >= 0) + l->audioPluginParameterChanged (this, paramIndex); + else + l->audioPluginChanged (this); + + i = jmin (i, listeners.size()); + } + } +} //============================================================================== bool JUCE_CALLTYPE AudioPluginInstance::getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info) @@ -76,7 +122,34 @@ 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); +} + +void JUCE_CALLTYPE AudioPluginInstance::updateHostDisplay() { + queueChangeMessage (-1); +} + +void AudioPluginInstance::queueChangeMessage (const int index) throw() +{ + const ScopedLock sl (changedParamLock); + changedParams.addIfNotAlreadyThere (index); + if (! internalAsyncUpdater->isTimerRunning()) + internalAsyncUpdater->startTimer (1); } + +//============================================================================== +AudioPluginInstance::InternalAsyncUpdater::InternalAsyncUpdater (AudioPluginInstance& owner_) + : owner (owner_) +{ +} + +void AudioPluginInstance::InternalAsyncUpdater::timerCallback() +{ + stopTimer(); + owner.internalAsyncCallback(); +} + diff --git a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h index 6c7e97a888..1223ecf2f4 100644 --- a/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h +++ b/extras/audio plugin host/src/plugins/juce_AudioPluginInstance.h @@ -33,6 +33,27 @@ #define __JUCE_AUDIOPLUGININSTANCE_JUCEHEADER__ #include "../../../audio plugins/wrapper/juce_AudioFilterBase.h" +class AudioPluginInstance; + + +//============================================================================== +class AudioPluginParameterListener +{ +public: + //============================================================================== + /** Destructor. */ + virtual ~AudioPluginParameterListener() {} + + //============================================================================== + /** Receives a callback when a parameter is changed. */ + virtual void audioPluginParameterChanged (AudioPluginInstance* plugin, + int parameterIndex) = 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; +}; //============================================================================== @@ -55,7 +76,7 @@ public: Make sure that you delete any UI components that belong to this plugin before deleting the plugin. */ - virtual ~AudioPluginInstance() {} + virtual ~AudioPluginInstance(); //============================================================================== /** Returns the plugin's name. */ @@ -109,14 +130,45 @@ public: */ virtual int getSamplesLatency() const = 0; + + //============================================================================== + /** Adds a listener that will be called when one of this plugin's parameters changes. */ + void addListener (AudioPluginParameterListener* const newListener) throw(); + + /** Removes a previously added listener. */ + void removeListener (AudioPluginParameterListener* const listenerToRemove) throw(); + //============================================================================== juce_UseDebuggingNewOperator protected: - AudioPluginInstance() {} + VoidArray listeners; + CriticalSection changedParamLock; + Array changedParams; + + class InternalAsyncUpdater : public Timer + { + public: + InternalAsyncUpdater (AudioPluginInstance& owner); + ~InternalAsyncUpdater() {} + + void timerCallback(); + + juce_UseDebuggingNewOperator + + private: + AudioPluginInstance& owner; + }; + + InternalAsyncUpdater* internalAsyncUpdater; + void internalAsyncCallback(); + void queueChangeMessage (const int index) throw(); + + AudioPluginInstance(); bool JUCE_CALLTYPE getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info); void JUCE_CALLTYPE informHostOfParameterChange (int index, float newValue); + void JUCE_CALLTYPE updateHostDisplay(); }; diff --git a/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp b/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp index c22b23563b..a202a3a78e 100644 --- a/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp +++ b/extras/audio plugin host/src/plugins/juce_GenericAudioFilterEditor.cpp @@ -34,20 +34,24 @@ //============================================================================== -class FilterParameterPropertyComp : public PropertyComponent +class FilterParameterPropertyComp : public PropertyComponent, + public AudioPluginParameterListener { public: - FilterParameterPropertyComp (AudioPluginInstance* const filter_, + FilterParameterPropertyComp (const String& name, + AudioPluginInstance* const filter_, const int index_) - : PropertyComponent (filter_->getParameterName (index_)), + : PropertyComponent (name), filter (filter_), index (index_) { addAndMakeVisible (slider = new PluginSlider (filter_, index_)); + filter->addListener (this); } ~FilterParameterPropertyComp() { + filter->removeListener (this); deleteAllChildren(); } @@ -56,6 +60,16 @@ public: slider->setValue (filter->getParameter (index), false); } + void audioPluginChanged (AudioPluginInstance*) + { + } + + void audioPluginParameterChanged (AudioPluginInstance*, int parameterIndex) + { + if (parameterIndex == index) + refresh(); + } + //============================================================================== juce_UseDebuggingNewOperator @@ -76,6 +90,7 @@ private: setRange (0.0, 1.0, 0.0); setSliderStyle (Slider::LinearBar); setTextBoxIsEditable (false); + setScrollWheelEnabled (false); } ~PluginSlider() @@ -120,7 +135,11 @@ GenericAudioFilterEditor::GenericAudioFilterEditor (AudioPluginInstance* const f for (int i = 0; i < numParams; ++i) { - FilterParameterPropertyComp* const pc = new FilterParameterPropertyComp (filter, i); + String name (filter->getParameterName (i)); + if (name.trim().isEmpty()) + name = "Unnamed"; + + FilterParameterPropertyComp* const pc = new FilterParameterPropertyComp (name, filter, i); params.add (pc); totalHeight += pc->getPreferredHeight(); } diff --git a/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp b/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp index f60beccca1..6186889191 100644 --- a/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp +++ b/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp @@ -48,6 +48,10 @@ #define _clearfp() #endif +BEGIN_JUCE_NAMESPACE + extern void juce_callAnyTimersSynchronously(); +END_JUCE_NAMESPACE + //============================================================================== const int fxbVersionNum = 1; @@ -288,15 +292,17 @@ public: void close() { + _fpreset(); // (doesn't do any harm) + if (hModule != 0) { - try + __try { - _fpreset(); // (doesn't do any harm) FreeLibrary (hModule); } - catch (...) - {} + __finally + { + } } } @@ -612,6 +618,7 @@ VSTPluginInstance::~VSTPluginInstance() {} } + module = 0; effect = 0; } @@ -1409,7 +1416,9 @@ AudioFilterEditor* JUCE_CALLTYPE VSTPluginInstance::createEditor() //============================================================================== void VSTPluginInstance::handleAsyncUpdate() { - // called asynchronously to indicate that the plugin's parameters have changed.. + // indicates that something about the plugin has changed.. + if (callbacks != 0) + callbacks->updateHostDisplay(); } //============================================================================== @@ -1786,7 +1795,8 @@ VstIntPtr VSTPluginInstance::handleCallback (VstInt32 opcode, VstInt32 index, Vs switch (opcode) { case audioMasterAutomate: - // index = param num, opt = value + if (callbacks != 0) + callbacks->informHostOfParameterChange (index, opt); break; case audioMasterProcessEvents: @@ -1807,14 +1817,24 @@ VstIntPtr VSTPluginInstance::handleCallback (VstInt32 opcode, VstInt32 index, Vs break; case audioMasterIdle: - Thread::sleep (1); - if (! isTimerRunning()) - startTimer (50); - + if (insideVSTCallback == 0 && MessageManager::getInstance()->isThisTheMessageThread()) + { + ++insideVSTCallback; #if JUCE_MAC - if (getActiveEditor() != 0) - dispatch (effEditIdle, 0, 0, 0, 0); + if (getActiveEditor() != 0) + dispatch (effEditIdle, 0, 0, 0, 0); #endif + const MessageManagerLock mml; + + juce_callAnyTimersSynchronously(); + + handleUpdateNowIfNeeded(); + + for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); + + --insideVSTCallback; + } break; case audioMasterUpdateDisplay: diff --git a/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp b/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp index c2679a36f9..0dbaf92035 100644 --- a/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp @@ -1,846 +1,851 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - JUCE is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include -#include "AUMIDIEffectBase.h" -#include "AUCarbonViewBase.h" -#include "../../juce_AudioFilterBase.h" -#include "../../juce_IncludeCharacteristics.h" - - -//============================================================================== -#define juceFilterObjectPropertyID 0x1a45ffe9 -static VoidArray activePlugins; - -static const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; -static const int numChannelConfigs = numElementsInArray (channelConfigs); - -BEGIN_JUCE_NAMESPACE -extern void juce_setCurrentExecutableFileNameFromBundleId (const String& bundleId) throw(); -END_JUCE_NAMESPACE - - -//============================================================================== -class JuceAU : public AUMIDIEffectBase, - public AudioFilterBase::FilterNativeCallbacks -{ -public: - //============================================================================== - JuceAU (AudioUnit component) - : AUMIDIEffectBase (component), - juceFilter (0), - bufferSpace (2, 16), - channels (0), - prepared (false) - { - CreateElements(); - - if (activePlugins.size() == 0) - { - initialiseJuce_GUI(); - -#ifdef JucePlugin_CFBundleIdentifier - juce_setCurrentExecutableFileNameFromBundleId (JucePlugin_CFBundleIdentifier); -#endif - - MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (0); - } - - juceFilter = createPluginFilter(); - juceFilter->initialiseInternal (this); - - jassert (juceFilter != 0); - Globals()->UseIndexedParameters (juceFilter->getNumParameters()); - - activePlugins.add (this); - } - - ~JuceAU() - { - delete juceFilter; - juceFilter = 0; - - juce_free (channels); - channels = 0; - - jassert (activePlugins.contains (this)); - activePlugins.removeValue (this); - - if (activePlugins.size() == 0) - shutdownJuce_GUI(); - } - - //============================================================================== - ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - UInt32& outDataSize, - Boolean& outWritable) - { - if (inScope == kAudioUnitScope_Global) - { - if (inID == juceFilterObjectPropertyID) - { - outWritable = false; - outDataSize = sizeof (void*); - return noErr; - } - } - - return AUMIDIEffectBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); - } - - ComponentResult GetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - void* outData) - { - if (inScope == kAudioUnitScope_Global) - { - if (inID == juceFilterObjectPropertyID) - { - *((void**) outData) = (void*) juceFilter; - return noErr; - } - } - - return AUMIDIEffectBase::GetProperty (inID, inScope, inElement, outData); - } - - ComponentResult SaveState (CFPropertyListRef* outData) - { - ComponentResult err = AUMIDIEffectBase::SaveState (outData); - - if (err != noErr) - return err; - - jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); - - CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; - - if (juceFilter != 0) - { - JUCE_NAMESPACE::MemoryBlock state; - juceFilter->getStateInformation (state); - - if (state.getSize() > 0) - { - CFDataRef ourState = CFDataCreate (kCFAllocatorDefault, (const uint8*) state, state.getSize()); - CFDictionarySetValue (dict, CFSTR("jucePluginState"), ourState); - CFRelease (ourState); - } - } - - return noErr; - } - - ComponentResult RestoreState (CFPropertyListRef inData) - { - ComponentResult err = AUMIDIEffectBase::RestoreState (inData); - - if (err != noErr) - return err; - - if (juceFilter != 0) - { - CFDictionaryRef dict = (CFDictionaryRef) inData; - CFDataRef data = 0; - - if (CFDictionaryGetValueIfPresent (dict, CFSTR("jucePluginState"), - (const void**) &data)) - { - if (data != 0) - { - const int numBytes = (int) CFDataGetLength (data); - const uint8* const rawBytes = CFDataGetBytePtr (data); - - if (numBytes > 0) - juceFilter->setStateInformation (rawBytes, numBytes); - } - } - } - - return noErr; - } - - UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) - { - if (juceFilter == 0) - return 0; - - // You need to actually add some configurations to the JucePlugin_PreferredChannelConfigurations - // value in your JucePluginCharacteristics.h file.. - jassert (numChannelConfigs > 0); - - if (outInfo != 0) - { - for (int i = 0; i < numChannelConfigs; ++i) - { - channelInfo[i].inChannels = channelConfigs[i][0]; - channelInfo[i].outChannels = channelConfigs[i][1]; - - outInfo[i] = channelInfo + i; - } - } - - return numChannelConfigs; - } - - //============================================================================== - ComponentResult GetParameterInfo (AudioUnitScope inScope, - AudioUnitParameterID inParameterID, - AudioUnitParameterInfo& outParameterInfo) - { - if (inScope == kAudioUnitScope_Global && juceFilter != 0) - { - outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable - | kAudioUnitParameterFlag_IsReadable - | kAudioUnitParameterFlag_HasCFNameString; - - outParameterInfo.name[0] = 0; - outParameterInfo.cfNameString = PlatformUtilities::juceStringToCFString (juceFilter->getParameterName ((int) inParameterID)); - outParameterInfo.minValue = 0.0f; - outParameterInfo.maxValue = 1.0f; - outParameterInfo.defaultValue = 0.0f; - outParameterInfo.unit = kAudioUnitParameterUnit_Generic; - - return noErr; - } - else - { - return kAudioUnitErr_InvalidParameter; - } - } - - ComponentResult GetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32& outValue) - { - if (inScope == kAudioUnitScope_Global && juceFilter != 0) - { - outValue = juceFilter->getParameter ((int) inID); - return noErr; - } - - return AUBase::GetParameter (inID, inScope, inElement, outValue); - } - - ComponentResult SetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32 inValue, - UInt32 inBufferOffsetInFrames) - { - if (inScope == kAudioUnitScope_Global && juceFilter != 0) - { - juceFilter->setParameter ((int) inID, inValue); - return noErr; - } - - return AUBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); - } - - //============================================================================== - ComponentResult Version() { return JucePlugin_VersionCode; } - - bool SupportsTail() { return true; } - Float64 GetTailTime() { return 0; } - - Float64 GetLatency() - { - jassert (GetSampleRate() > 0); - - if (GetSampleRate() <= 0) - return 0.0; - - return (JucePlugin_Latency) / GetSampleRate(); - } - - //============================================================================== - int GetNumCustomUIComponents() { return 1; } - - void GetUIComponentDescs (ComponentDescription* inDescArray) - { - inDescArray[0].componentType = kAudioUnitCarbonViewComponentType; - inDescArray[0].componentSubType = JucePlugin_AUSubType; - inDescArray[0].componentManufacturer = JucePlugin_AUManufacturerCode; - inDescArray[0].componentFlags = 0; - inDescArray[0].componentFlagsMask = 0; - } - - //============================================================================== - bool getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info) - { - info.timeSigNumerator = 0; - info.timeSigDenominator = 0; - info.timeInSeconds = 0; - info.editOriginTime = 0; - info.ppqPositionOfLastBarStart = 0; - info.isPlaying = false; - info.isRecording = false; - - switch (lastSMPTETime.mType) - { - case kSMPTETimeType24: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps24; - break; - - case kSMPTETimeType25: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps25; - break; - - case kSMPTETimeType30Drop: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30drop; - break; - - case kSMPTETimeType30: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30; - break; - - case kSMPTETimeType2997: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997; - break; - - case kSMPTETimeType2997Drop: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997drop; - break; - - //case kSMPTETimeType60: - //case kSMPTETimeType5994: - default: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fpsUnknown; - break; - } - - if (CallHostBeatAndTempo (&info.ppqPosition, &info.bpm) != noErr) - { - info.ppqPosition = 0; - info.bpm = 0; - } - - UInt32 outDeltaSampleOffsetToNextBeat; - double outCurrentMeasureDownBeat; - float num; - UInt32 den; - - if (CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, &num, &den, - &outCurrentMeasureDownBeat) == noErr) - { - info.timeSigNumerator = (int) num; - info.timeSigDenominator = den; - info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; - } - - double outCurrentSampleInTimeLine, outCycleStartBeat, outCycleEndBeat; - Boolean playing, playchanged, looping; - - if (CallHostTransportState (&playing, - &playchanged, - &outCurrentSampleInTimeLine, - &looping, - &outCycleStartBeat, - &outCycleEndBeat) == noErr) - { - info.isPlaying = playing; - info.timeInSeconds = outCurrentSampleInTimeLine / GetSampleRate(); - } - - return true; - } - - 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); - } - } - } - - //============================================================================== - ComponentResult Initialize() - { - AUMIDIEffectBase::Initialize(); - prepareToPlay(); - return noErr; - } - - void Cleanup() - { - AUMIDIEffectBase::Cleanup(); - - if (juceFilter != 0) - juceFilter->releaseResources(); - - bufferSpace.setSize (2, 16); - midiEvents.clear(); - prepared = false; - } - - ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) - { - if (! prepared) - prepareToPlay(); - - return AUMIDIEffectBase::Reset (inScope, inElement); - } - - void prepareToPlay() - { - if (juceFilter != 0) - { - juceFilter->numInputChannels = GetInput(0)->GetStreamFormat().mChannelsPerFrame; - juceFilter->numOutputChannels = GetOutput(0)->GetStreamFormat().mChannelsPerFrame; - - bufferSpace.setSize (juceFilter->numInputChannels + juceFilter->numOutputChannels, - GetMaxFramesPerSlice() + 32); - - juceFilter->prepareToPlay (GetSampleRate(), - GetMaxFramesPerSlice()); - - midiEvents.clear(); - - juce_free (channels); - channels = (float**) juce_calloc (sizeof (float*) * jmax (juceFilter->numInputChannels, - juceFilter->numOutputChannels) + 4); - - prepared = true; - } - } - - ComponentResult Render (AudioUnitRenderActionFlags &ioActionFlags, - const AudioTimeStamp& inTimeStamp, - UInt32 nFrames) - { - lastSMPTETime = inTimeStamp.mSMPTETime; - - return AUMIDIEffectBase::Render (ioActionFlags, inTimeStamp, nFrames); - } - - - OSStatus ProcessBufferLists (AudioUnitRenderActionFlags& ioActionFlags, - const AudioBufferList& inBuffer, - AudioBufferList& outBuffer, - UInt32 numSamples) - { - if (juceFilter != 0) - { - jassert (prepared); - - int numOutChans = 0; - int nextSpareBufferChan = 0; - bool needToReinterleave = false; - const int numIn = juceFilter->numInputChannels; - const int numOut = juceFilter->numOutputChannels; - - unsigned int i; - for (i = 0; i < outBuffer.mNumberBuffers; ++i) - { - AudioBuffer& buf = outBuffer.mBuffers[i]; - - if (buf.mNumberChannels == 1) - { - channels [numOutChans++] = (float*) buf.mData; - } - else - { - needToReinterleave = true; - - for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numOutChans < numOut; ++subChan) - channels [numOutChans++] = bufferSpace.getSampleData (nextSpareBufferChan++); - } - - if (numOutChans >= numOut) - break; - } - - int numInChans = 0; - - for (i = 0; i < inBuffer.mNumberBuffers; ++i) - { - const AudioBuffer& buf = inBuffer.mBuffers[i]; - - if (buf.mNumberChannels == 1) - { - if (numInChans < numOut) - memcpy (channels [numInChans], (const float*) buf.mData, sizeof (float) * numSamples); - else - channels [numInChans] = (float*) buf.mData; - } - else - { - // need to de-interleave.. - for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numInChans < numIn; ++subChan) - { - float* dest; - - if (numInChans >= numOut) - { - dest = bufferSpace.getSampleData (nextSpareBufferChan++); - channels [numInChans++] = dest; - } - else - { - dest = channels [numInChans++]; - } - - const float* src = ((const float*) buf.mData) + subChan; - - for (int j = numSamples; --j >= 0;) - { - *dest++ = *src; - src += buf.mNumberChannels; - } - } - } - - if (numInChans >= numIn) - break; - } - - { - AudioSampleBuffer buffer (channels, jmax (numIn, numOut), numSamples); - - const ScopedLock sl (juceFilter->getCallbackLock()); - - if (juceFilter->suspended) - { - for (int i = 0; i < numOut; ++i) - zeromem (channels [i], sizeof (float) * numSamples); - } - else - { - juceFilter->processBlock (buffer, midiEvents); - } - } - - if (! midiEvents.isEmpty()) - { -#if JucePlugin_ProducesMidiOutput - const uint8* midiEventData; - int midiEventSize, midiEventPosition; - MidiBuffer::Iterator i (midiEvents); - - while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition)) - { - jassert (midiEventPosition >= 0 && midiEventPosition < (int) numSamples); - - - - //xxx - } -#else - // if your plugin creates midi messages, you'll need to set - // the JucePlugin_ProducesMidiOutput macro to 1 in your - // JucePluginCharacteristics.h file - //jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); -#endif - midiEvents.clear(); - } - - if (needToReinterleave) - { - nextSpareBufferChan = 0; - - for (i = 0; i < outBuffer.mNumberBuffers; ++i) - { - AudioBuffer& buf = outBuffer.mBuffers[i]; - - if (buf.mNumberChannels > 1) - { - for (unsigned int subChan = 0; subChan < buf.mNumberChannels; ++subChan) - { - const float* src = bufferSpace.getSampleData (nextSpareBufferChan++); - float* dest = ((float*) buf.mData) + subChan; - - for (int j = numSamples; --j >= 0;) - { - *dest = *src++; - dest += buf.mNumberChannels; - } - } - } - } - } - -#if ! JucePlugin_SilenceInProducesSilenceOut - ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; -#endif - } - - return noErr; - } - -protected: - OSStatus HandleMidiEvent (UInt8 nStatus, - UInt8 inChannel, - UInt8 inData1, - UInt8 inData2, - long inStartFrame) - { -#if JucePlugin_WantsMidiInput - uint8 data [4]; - data[0] = nStatus | inChannel; - data[1] = inData1; - data[2] = inData2; - - midiEvents.addEvent (data, 3, inStartFrame); -#endif - - return noErr; - } - - //============================================================================== -private: - AudioFilterBase* juceFilter; - AudioSampleBuffer bufferSpace; - float** channels; - MidiBuffer midiEvents; - bool prepared; - SMPTETime lastSMPTETime; - AUChannelInfo channelInfo [numChannelConfigs]; -}; - - -//============================================================================== -class JuceAUComponentHolder : public Component -{ -public: - JuceAUComponentHolder (Component* const editorComp) - { - addAndMakeVisible (editorComp); - setOpaque (true); - setVisible (true); - setBroughtToFrontOnMouseClick (true); - -#if ! JucePlugin_EditorRequiresKeyboardFocus - setWantsKeyboardFocus (false); -#endif - } - - ~JuceAUComponentHolder() - { - } - - void resized() - { - if (getNumChildComponents() > 0) - getChildComponent (0)->setBounds (0, 0, getWidth(), getHeight()); - } - - void paint (Graphics& g) - { - } -}; - -//============================================================================== -class JuceAUView : public AUCarbonViewBase, - public ComponentListener, - public MouseListener, - public Timer -{ - AudioFilterBase* juceFilter; - AudioFilterEditor* editorComp; - Component* windowComp; - bool recursive; - int mx, my; - -public: - JuceAUView (AudioUnitCarbonView auview) - : AUCarbonViewBase (auview), - juceFilter (0), - editorComp (0), - windowComp (0), - recursive (false), - mx (0), - my (0) - { - } - - ~JuceAUView() - { - deleteUI(); - } - - ComponentResult CreateUI (Float32 inXOffset, Float32 inYOffset) - { - if (juceFilter == 0) - { - UInt32 propertySize = sizeof (&juceFilter); - - AudioUnitGetProperty (GetEditAudioUnit(), - juceFilterObjectPropertyID, - kAudioUnitScope_Global, - 0, - &juceFilter, - &propertySize); - } - - if (juceFilter != 0) - { - deleteUI(); - - editorComp = juceFilter->createEditorIfNeeded(); - - const int w = editorComp->getWidth(); - const int h = editorComp->getHeight(); - - editorComp->setOpaque (true); - editorComp->setVisible (true); - - windowComp = new JuceAUComponentHolder (editorComp); - windowComp->setBounds ((int) inXOffset, (int) inYOffset, w, h); - - windowComp->addToDesktop (0, (void*) mCarbonPane); - SizeControl (mCarbonPane, w, h); - - editorComp->addComponentListener (this); - windowComp->addMouseListener (this, true); - - startTimer (20); - } - else - { - jassertfalse // can't get a pointer to our effect - } - - return noErr; - } - - void componentMovedOrResized (Component& component, - bool wasMoved, - bool wasResized) - { - if (! recursive) - { - recursive = true; - - if (editorComp != 0 && wasResized) - { - const int w = jmax (32, editorComp->getWidth()); - const int h = jmax (32, editorComp->getHeight()); - - SizeControl (mCarbonPane, w, h); - - if (windowComp->getWidth() != w - || windowComp->getHeight() != h) - { - windowComp->setSize (w, h); - } - - editorComp->repaint(); - } - - recursive = false; - } - } - - void timerCallback() - { - // for some stupid Apple-related reason, mouse move events just don't seem to get sent - // to the windows in an AU, so we have to bodge it here and simulate them with a - // timer.. - if (editorComp != 0) - { - int x, y; - Desktop::getInstance().getMousePosition (x, y); - - if (x != mx || y != my) - { - mx = x; - my = y; - - if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) - { - for (int i = ComponentPeer::getNumPeers(); --i >= 0;) - { - ComponentPeer* const peer = ComponentPeer::getPeer (i); - - const int rx = x - peer->getComponent()->getX(); - const int ry = y - peer->getComponent()->getY(); - - if (peer->contains (rx, ry, false) && peer->getComponent()->isShowing()) - { - peer->handleMouseMove (rx, ry, Time::currentTimeMillis()); - break; - } - } - } - } - } - } - - void mouseMove (const MouseEvent& e) - { - Desktop::getInstance().getMousePosition (mx, my); - startTimer (20); - } - -private: - void deleteUI() - { - PopupMenu::dismissAllActiveMenus(); - - // there's some kind of component currently modal, but the host - // is trying to delete our plugin.. - jassert (Component::getCurrentlyModalComponent() == 0); - - if (editorComp != 0) - juceFilter->editorBeingDeleted (editorComp); - - deleteAndZero (editorComp); - deleteAndZero (windowComp); - } -}; - -//============================================================================== -#define JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) \ -extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj); \ -extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj) \ -{ \ - return ComponentEntryPoint::Dispatch(params, obj); \ -} - -#define JUCE_COMPONENT_ENTRY(Class, Name, Suffix) JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) - -JUCE_COMPONENT_ENTRY (JuceAU, JucePlugin_AUExportPrefix, Entry) -JUCE_COMPONENT_ENTRY (JuceAUView, JucePlugin_AUExportPrefix, ViewEntry) +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + JUCE is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include +#include "AUMIDIEffectBase.h" +#include "AUCarbonViewBase.h" +#include "../../juce_AudioFilterBase.h" +#include "../../juce_IncludeCharacteristics.h" + + +//============================================================================== +#define juceFilterObjectPropertyID 0x1a45ffe9 +static VoidArray activePlugins; + +static const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; +static const int numChannelConfigs = numElementsInArray (channelConfigs); + +BEGIN_JUCE_NAMESPACE +extern void juce_setCurrentExecutableFileNameFromBundleId (const String& bundleId) throw(); +END_JUCE_NAMESPACE + + +//============================================================================== +class JuceAU : public AUMIDIEffectBase, + public AudioFilterBase::FilterNativeCallbacks +{ +public: + //============================================================================== + JuceAU (AudioUnit component) + : AUMIDIEffectBase (component), + juceFilter (0), + bufferSpace (2, 16), + channels (0), + prepared (false) + { + CreateElements(); + + if (activePlugins.size() == 0) + { + initialiseJuce_GUI(); + +#ifdef JucePlugin_CFBundleIdentifier + juce_setCurrentExecutableFileNameFromBundleId (JucePlugin_CFBundleIdentifier); +#endif + + MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (0); + } + + juceFilter = createPluginFilter(); + juceFilter->initialiseInternal (this); + + jassert (juceFilter != 0); + Globals()->UseIndexedParameters (juceFilter->getNumParameters()); + + activePlugins.add (this); + } + + ~JuceAU() + { + delete juceFilter; + juceFilter = 0; + + juce_free (channels); + channels = 0; + + jassert (activePlugins.contains (this)); + activePlugins.removeValue (this); + + if (activePlugins.size() == 0) + shutdownJuce_GUI(); + } + + //============================================================================== + ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + UInt32& outDataSize, + Boolean& outWritable) + { + if (inScope == kAudioUnitScope_Global) + { + if (inID == juceFilterObjectPropertyID) + { + outWritable = false; + outDataSize = sizeof (void*); + return noErr; + } + } + + return AUMIDIEffectBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); + } + + ComponentResult GetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + void* outData) + { + if (inScope == kAudioUnitScope_Global) + { + if (inID == juceFilterObjectPropertyID) + { + *((void**) outData) = (void*) juceFilter; + return noErr; + } + } + + return AUMIDIEffectBase::GetProperty (inID, inScope, inElement, outData); + } + + ComponentResult SaveState (CFPropertyListRef* outData) + { + ComponentResult err = AUMIDIEffectBase::SaveState (outData); + + if (err != noErr) + return err; + + jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); + + CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; + + if (juceFilter != 0) + { + JUCE_NAMESPACE::MemoryBlock state; + juceFilter->getStateInformation (state); + + if (state.getSize() > 0) + { + CFDataRef ourState = CFDataCreate (kCFAllocatorDefault, (const uint8*) state, state.getSize()); + CFDictionarySetValue (dict, CFSTR("jucePluginState"), ourState); + CFRelease (ourState); + } + } + + return noErr; + } + + ComponentResult RestoreState (CFPropertyListRef inData) + { + ComponentResult err = AUMIDIEffectBase::RestoreState (inData); + + if (err != noErr) + return err; + + if (juceFilter != 0) + { + CFDictionaryRef dict = (CFDictionaryRef) inData; + CFDataRef data = 0; + + if (CFDictionaryGetValueIfPresent (dict, CFSTR("jucePluginState"), + (const void**) &data)) + { + if (data != 0) + { + const int numBytes = (int) CFDataGetLength (data); + const uint8* const rawBytes = CFDataGetBytePtr (data); + + if (numBytes > 0) + juceFilter->setStateInformation (rawBytes, numBytes); + } + } + } + + return noErr; + } + + UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) + { + if (juceFilter == 0) + return 0; + + // You need to actually add some configurations to the JucePlugin_PreferredChannelConfigurations + // value in your JucePluginCharacteristics.h file.. + jassert (numChannelConfigs > 0); + + if (outInfo != 0) + { + for (int i = 0; i < numChannelConfigs; ++i) + { + channelInfo[i].inChannels = channelConfigs[i][0]; + channelInfo[i].outChannels = channelConfigs[i][1]; + + outInfo[i] = channelInfo + i; + } + } + + return numChannelConfigs; + } + + //============================================================================== + ComponentResult GetParameterInfo (AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + AudioUnitParameterInfo& outParameterInfo) + { + if (inScope == kAudioUnitScope_Global && juceFilter != 0) + { + outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable + | kAudioUnitParameterFlag_IsReadable + | kAudioUnitParameterFlag_HasCFNameString; + + outParameterInfo.name[0] = 0; + outParameterInfo.cfNameString = PlatformUtilities::juceStringToCFString (juceFilter->getParameterName ((int) inParameterID)); + outParameterInfo.minValue = 0.0f; + outParameterInfo.maxValue = 1.0f; + outParameterInfo.defaultValue = 0.0f; + outParameterInfo.unit = kAudioUnitParameterUnit_Generic; + + return noErr; + } + else + { + return kAudioUnitErr_InvalidParameter; + } + } + + ComponentResult GetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32& outValue) + { + if (inScope == kAudioUnitScope_Global && juceFilter != 0) + { + outValue = juceFilter->getParameter ((int) inID); + return noErr; + } + + return AUBase::GetParameter (inID, inScope, inElement, outValue); + } + + ComponentResult SetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32 inValue, + UInt32 inBufferOffsetInFrames) + { + if (inScope == kAudioUnitScope_Global && juceFilter != 0) + { + juceFilter->setParameter ((int) inID, inValue); + return noErr; + } + + return AUBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); + } + + //============================================================================== + ComponentResult Version() { return JucePlugin_VersionCode; } + + bool SupportsTail() { return true; } + Float64 GetTailTime() { return 0; } + + Float64 GetLatency() + { + jassert (GetSampleRate() > 0); + + if (GetSampleRate() <= 0) + return 0.0; + + return (JucePlugin_Latency) / GetSampleRate(); + } + + //============================================================================== + int GetNumCustomUIComponents() { return 1; } + + void GetUIComponentDescs (ComponentDescription* inDescArray) + { + inDescArray[0].componentType = kAudioUnitCarbonViewComponentType; + inDescArray[0].componentSubType = JucePlugin_AUSubType; + inDescArray[0].componentManufacturer = JucePlugin_AUManufacturerCode; + inDescArray[0].componentFlags = 0; + inDescArray[0].componentFlagsMask = 0; + } + + //============================================================================== + bool getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info) + { + info.timeSigNumerator = 0; + info.timeSigDenominator = 0; + info.timeInSeconds = 0; + info.editOriginTime = 0; + info.ppqPositionOfLastBarStart = 0; + info.isPlaying = false; + info.isRecording = false; + + switch (lastSMPTETime.mType) + { + case kSMPTETimeType24: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps24; + break; + + case kSMPTETimeType25: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps25; + break; + + case kSMPTETimeType30Drop: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30drop; + break; + + case kSMPTETimeType30: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30; + break; + + case kSMPTETimeType2997: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997; + break; + + case kSMPTETimeType2997Drop: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997drop; + break; + + //case kSMPTETimeType60: + //case kSMPTETimeType5994: + default: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fpsUnknown; + break; + } + + if (CallHostBeatAndTempo (&info.ppqPosition, &info.bpm) != noErr) + { + info.ppqPosition = 0; + info.bpm = 0; + } + + UInt32 outDeltaSampleOffsetToNextBeat; + double outCurrentMeasureDownBeat; + float num; + UInt32 den; + + if (CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, &num, &den, + &outCurrentMeasureDownBeat) == noErr) + { + info.timeSigNumerator = (int) num; + info.timeSigDenominator = den; + info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; + } + + double outCurrentSampleInTimeLine, outCycleStartBeat, outCycleEndBeat; + Boolean playing, playchanged, looping; + + if (CallHostTransportState (&playing, + &playchanged, + &outCurrentSampleInTimeLine, + &looping, + &outCycleStartBeat, + &outCycleEndBeat) == noErr) + { + info.isPlaying = playing; + info.timeInSeconds = outCurrentSampleInTimeLine / GetSampleRate(); + } + + return true; + } + + 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); + } + } + } + + void updateHostDisplay() + { + // xxx is there an AU equivalent? + } + + //============================================================================== + ComponentResult Initialize() + { + AUMIDIEffectBase::Initialize(); + prepareToPlay(); + return noErr; + } + + void Cleanup() + { + AUMIDIEffectBase::Cleanup(); + + if (juceFilter != 0) + juceFilter->releaseResources(); + + bufferSpace.setSize (2, 16); + midiEvents.clear(); + prepared = false; + } + + ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) + { + if (! prepared) + prepareToPlay(); + + return AUMIDIEffectBase::Reset (inScope, inElement); + } + + void prepareToPlay() + { + if (juceFilter != 0) + { + juceFilter->numInputChannels = GetInput(0)->GetStreamFormat().mChannelsPerFrame; + juceFilter->numOutputChannels = GetOutput(0)->GetStreamFormat().mChannelsPerFrame; + + bufferSpace.setSize (juceFilter->numInputChannels + juceFilter->numOutputChannels, + GetMaxFramesPerSlice() + 32); + + juceFilter->prepareToPlay (GetSampleRate(), + GetMaxFramesPerSlice()); + + midiEvents.clear(); + + juce_free (channels); + channels = (float**) juce_calloc (sizeof (float*) * jmax (juceFilter->numInputChannels, + juceFilter->numOutputChannels) + 4); + + prepared = true; + } + } + + ComponentResult Render (AudioUnitRenderActionFlags &ioActionFlags, + const AudioTimeStamp& inTimeStamp, + UInt32 nFrames) + { + lastSMPTETime = inTimeStamp.mSMPTETime; + + return AUMIDIEffectBase::Render (ioActionFlags, inTimeStamp, nFrames); + } + + + OSStatus ProcessBufferLists (AudioUnitRenderActionFlags& ioActionFlags, + const AudioBufferList& inBuffer, + AudioBufferList& outBuffer, + UInt32 numSamples) + { + if (juceFilter != 0) + { + jassert (prepared); + + int numOutChans = 0; + int nextSpareBufferChan = 0; + bool needToReinterleave = false; + const int numIn = juceFilter->numInputChannels; + const int numOut = juceFilter->numOutputChannels; + + unsigned int i; + for (i = 0; i < outBuffer.mNumberBuffers; ++i) + { + AudioBuffer& buf = outBuffer.mBuffers[i]; + + if (buf.mNumberChannels == 1) + { + channels [numOutChans++] = (float*) buf.mData; + } + else + { + needToReinterleave = true; + + for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numOutChans < numOut; ++subChan) + channels [numOutChans++] = bufferSpace.getSampleData (nextSpareBufferChan++); + } + + if (numOutChans >= numOut) + break; + } + + int numInChans = 0; + + for (i = 0; i < inBuffer.mNumberBuffers; ++i) + { + const AudioBuffer& buf = inBuffer.mBuffers[i]; + + if (buf.mNumberChannels == 1) + { + if (numInChans < numOut) + memcpy (channels [numInChans], (const float*) buf.mData, sizeof (float) * numSamples); + else + channels [numInChans] = (float*) buf.mData; + } + else + { + // need to de-interleave.. + for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numInChans < numIn; ++subChan) + { + float* dest; + + if (numInChans >= numOut) + { + dest = bufferSpace.getSampleData (nextSpareBufferChan++); + channels [numInChans++] = dest; + } + else + { + dest = channels [numInChans++]; + } + + const float* src = ((const float*) buf.mData) + subChan; + + for (int j = numSamples; --j >= 0;) + { + *dest++ = *src; + src += buf.mNumberChannels; + } + } + } + + if (numInChans >= numIn) + break; + } + + { + AudioSampleBuffer buffer (channels, jmax (numIn, numOut), numSamples); + + const ScopedLock sl (juceFilter->getCallbackLock()); + + if (juceFilter->suspended) + { + for (int i = 0; i < numOut; ++i) + zeromem (channels [i], sizeof (float) * numSamples); + } + else + { + juceFilter->processBlock (buffer, midiEvents); + } + } + + if (! midiEvents.isEmpty()) + { +#if JucePlugin_ProducesMidiOutput + const uint8* midiEventData; + int midiEventSize, midiEventPosition; + MidiBuffer::Iterator i (midiEvents); + + while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition)) + { + jassert (midiEventPosition >= 0 && midiEventPosition < (int) numSamples); + + + + //xxx + } +#else + // if your plugin creates midi messages, you'll need to set + // the JucePlugin_ProducesMidiOutput macro to 1 in your + // JucePluginCharacteristics.h file + //jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); +#endif + midiEvents.clear(); + } + + if (needToReinterleave) + { + nextSpareBufferChan = 0; + + for (i = 0; i < outBuffer.mNumberBuffers; ++i) + { + AudioBuffer& buf = outBuffer.mBuffers[i]; + + if (buf.mNumberChannels > 1) + { + for (unsigned int subChan = 0; subChan < buf.mNumberChannels; ++subChan) + { + const float* src = bufferSpace.getSampleData (nextSpareBufferChan++); + float* dest = ((float*) buf.mData) + subChan; + + for (int j = numSamples; --j >= 0;) + { + *dest = *src++; + dest += buf.mNumberChannels; + } + } + } + } + } + +#if ! JucePlugin_SilenceInProducesSilenceOut + ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; +#endif + } + + return noErr; + } + +protected: + OSStatus HandleMidiEvent (UInt8 nStatus, + UInt8 inChannel, + UInt8 inData1, + UInt8 inData2, + long inStartFrame) + { +#if JucePlugin_WantsMidiInput + uint8 data [4]; + data[0] = nStatus | inChannel; + data[1] = inData1; + data[2] = inData2; + + midiEvents.addEvent (data, 3, inStartFrame); +#endif + + return noErr; + } + + //============================================================================== +private: + AudioFilterBase* juceFilter; + AudioSampleBuffer bufferSpace; + float** channels; + MidiBuffer midiEvents; + bool prepared; + SMPTETime lastSMPTETime; + AUChannelInfo channelInfo [numChannelConfigs]; +}; + + +//============================================================================== +class JuceAUComponentHolder : public Component +{ +public: + JuceAUComponentHolder (Component* const editorComp) + { + addAndMakeVisible (editorComp); + setOpaque (true); + setVisible (true); + setBroughtToFrontOnMouseClick (true); + +#if ! JucePlugin_EditorRequiresKeyboardFocus + setWantsKeyboardFocus (false); +#endif + } + + ~JuceAUComponentHolder() + { + } + + void resized() + { + if (getNumChildComponents() > 0) + getChildComponent (0)->setBounds (0, 0, getWidth(), getHeight()); + } + + void paint (Graphics& g) + { + } +}; + +//============================================================================== +class JuceAUView : public AUCarbonViewBase, + public ComponentListener, + public MouseListener, + public Timer +{ + AudioFilterBase* juceFilter; + AudioFilterEditor* editorComp; + Component* windowComp; + bool recursive; + int mx, my; + +public: + JuceAUView (AudioUnitCarbonView auview) + : AUCarbonViewBase (auview), + juceFilter (0), + editorComp (0), + windowComp (0), + recursive (false), + mx (0), + my (0) + { + } + + ~JuceAUView() + { + deleteUI(); + } + + ComponentResult CreateUI (Float32 inXOffset, Float32 inYOffset) + { + if (juceFilter == 0) + { + UInt32 propertySize = sizeof (&juceFilter); + + AudioUnitGetProperty (GetEditAudioUnit(), + juceFilterObjectPropertyID, + kAudioUnitScope_Global, + 0, + &juceFilter, + &propertySize); + } + + if (juceFilter != 0) + { + deleteUI(); + + editorComp = juceFilter->createEditorIfNeeded(); + + const int w = editorComp->getWidth(); + const int h = editorComp->getHeight(); + + editorComp->setOpaque (true); + editorComp->setVisible (true); + + windowComp = new JuceAUComponentHolder (editorComp); + windowComp->setBounds ((int) inXOffset, (int) inYOffset, w, h); + + windowComp->addToDesktop (0, (void*) mCarbonPane); + SizeControl (mCarbonPane, w, h); + + editorComp->addComponentListener (this); + windowComp->addMouseListener (this, true); + + startTimer (20); + } + else + { + jassertfalse // can't get a pointer to our effect + } + + return noErr; + } + + void componentMovedOrResized (Component& component, + bool wasMoved, + bool wasResized) + { + if (! recursive) + { + recursive = true; + + if (editorComp != 0 && wasResized) + { + const int w = jmax (32, editorComp->getWidth()); + const int h = jmax (32, editorComp->getHeight()); + + SizeControl (mCarbonPane, w, h); + + if (windowComp->getWidth() != w + || windowComp->getHeight() != h) + { + windowComp->setSize (w, h); + } + + editorComp->repaint(); + } + + recursive = false; + } + } + + void timerCallback() + { + // for some stupid Apple-related reason, mouse move events just don't seem to get sent + // to the windows in an AU, so we have to bodge it here and simulate them with a + // timer.. + if (editorComp != 0) + { + int x, y; + Desktop::getInstance().getMousePosition (x, y); + + if (x != mx || y != my) + { + mx = x; + my = y; + + if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) + { + for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + { + ComponentPeer* const peer = ComponentPeer::getPeer (i); + + const int rx = x - peer->getComponent()->getX(); + const int ry = y - peer->getComponent()->getY(); + + if (peer->contains (rx, ry, false) && peer->getComponent()->isShowing()) + { + peer->handleMouseMove (rx, ry, Time::currentTimeMillis()); + break; + } + } + } + } + } + } + + void mouseMove (const MouseEvent& e) + { + Desktop::getInstance().getMousePosition (mx, my); + startTimer (20); + } + +private: + void deleteUI() + { + PopupMenu::dismissAllActiveMenus(); + + // there's some kind of component currently modal, but the host + // is trying to delete our plugin.. + jassert (Component::getCurrentlyModalComponent() == 0); + + if (editorComp != 0) + juceFilter->editorBeingDeleted (editorComp); + + deleteAndZero (editorComp); + deleteAndZero (windowComp); + } +}; + +//============================================================================== +#define JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) \ +extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj); \ +extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj) \ +{ \ + return ComponentEntryPoint::Dispatch(params, obj); \ +} + +#define JUCE_COMPONENT_ENTRY(Class, Name, Suffix) JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) + +JUCE_COMPONENT_ENTRY (JuceAU, JucePlugin_AUExportPrefix, Entry) +JUCE_COMPONENT_ENTRY (JuceAUView, JucePlugin_AUExportPrefix, ViewEntry) diff --git a/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp b/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp index 650682fd63..f07d4f7802 100644 --- a/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp @@ -845,6 +845,11 @@ protected: SetControlValue (index + 2, floatToLong (newValue)); } + void JUCE_CALLTYPE updateHostDisplay() + { + // xxx is there an RTAS equivalent? + } + //============================================================================== private: AudioFilterBase* juceFilter; diff --git a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp index c1469d6e5f..e1a1c84963 100644 --- a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp +++ b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp @@ -144,6 +144,10 @@ void AudioFilterStreamer::informHostOfParameterChange (int index, float newValue } +void JUCE_CALLTYPE AudioFilterStreamer::updateHostDisplay() +{ +} + //============================================================================== AudioFilterStreamingDeviceManager::AudioFilterStreamingDeviceManager() diff --git a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h index c4b61f3710..a4ff7aacff 100644 --- a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h +++ b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.h @@ -73,6 +73,7 @@ public: bool JUCE_CALLTYPE getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info); void JUCE_CALLTYPE informHostOfParameterChange (int index, float newValue); + void JUCE_CALLTYPE updateHostDisplay(); 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 0a89239ec7..b9f658d1d0 100644 --- a/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp @@ -101,9 +101,7 @@ static uint32 lastMasterIdleCall = 0; BEGIN_JUCE_NAMESPACE extern void juce_callAnyTimersSynchronously(); - #if JUCE_MAC - extern void juce_macDoPendingRepaintsNow(); - #elif JUCE_LINUX + #if JUCE_LINUX extern Display* display; extern bool juce_postMessageToSystemQueue (void* message); #endif @@ -809,6 +807,11 @@ public: setParameterAutomated (index, newValue); } + void JUCE_CALLTYPE updateHostDisplay() + { + updateDisplay(); + } + bool canParameterBeAutomated (VstInt32 index) { return filter->isParameterAutomatable ((int) index); diff --git a/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp b/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp index 0458ddc41d..390cdc5215 100644 --- a/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp +++ b/extras/audio plugins/wrapper/juce_AudioFilterBase.cpp @@ -38,8 +38,8 @@ AudioFilterBase::AudioFilterBase() blockSize (0), numInputChannels (0), numOutputChannels (0), - suspended (false), callbacks (0), + suspended (false), activeEditor (0) { } @@ -67,6 +67,12 @@ void AudioFilterBase::setParameterNotifyingHost (const int parameterIndex, setParameter (parameterIndex, newValue); } +void JUCE_CALLTYPE AudioFilterBase::updateHostDisplay() +{ + if (callbacks != 0) + callbacks->updateHostDisplay(); +} + bool AudioFilterBase::isParameterAutomatable (int /*index*/) const { return true; diff --git a/extras/audio plugins/wrapper/juce_AudioFilterBase.h b/extras/audio plugins/wrapper/juce_AudioFilterBase.h index 058add0782..2c7757b9f0 100644 --- a/extras/audio plugins/wrapper/juce_AudioFilterBase.h +++ b/extras/audio plugins/wrapper/juce_AudioFilterBase.h @@ -407,6 +407,13 @@ public: */ virtual bool isParameterAutomatable (int index) const; + /** The filter can call this when something (apart from a parameter value) has changed. + + It sends a hint to the host that something like the program, number of parameters, + etc, has changed, and that it should update itself. + */ + void JUCE_CALLTYPE updateHostDisplay(); + //============================================================================== /** Returns the number of preset programs the filter supports. @@ -492,6 +499,10 @@ public: virtual ~FilterNativeCallbacks() {} virtual bool JUCE_CALLTYPE getCurrentPositionInfo (CurrentPositionInfo& info) = 0; virtual void JUCE_CALLTYPE informHostOfParameterChange (int index, float newValue) = 0; + + /** Callback to indicate that something (other than a parameter) has changed in the + filter, such as its current program, parameter list, etc. */ + virtual void JUCE_CALLTYPE updateHostDisplay() = 0; }; @@ -534,6 +545,8 @@ protected: double sampleRate; /** @internal */ int blockSize, numInputChannels, numOutputChannels; + /** @internal */ + FilterNativeCallbacks* callbacks; private: friend class JuceVSTWrapper; @@ -546,8 +559,6 @@ private: CriticalSection callbackLock; bool suspended; - FilterNativeCallbacks* callbacks; - AudioFilterEditor* activeEditor; }; diff --git a/src/juce_appframework/documents/juce_FileBasedDocument.cpp b/src/juce_appframework/documents/juce_FileBasedDocument.cpp index 4339c19b87..3198d83ba1 100644 --- a/src/juce_appframework/documents/juce_FileBasedDocument.cpp +++ b/src/juce_appframework/documents/juce_FileBasedDocument.cpp @@ -271,7 +271,7 @@ FileBasedDocument::SaveResult FileBasedDocument::saveAsInteractive (const bool w if (chosen.getFileExtension().isEmpty()) chosen = chosen.withFileExtension (fileExtension); - return saveAs (f, false, false, true); + return saveAs (chosen, false, false, true); } return userCancelledSave; diff --git a/src/juce_appframework/gui/components/controls/juce_Slider.cpp b/src/juce_appframework/gui/components/controls/juce_Slider.cpp index 5ad08f6231..48c4e0c24c 100644 --- a/src/juce_appframework/gui/components/controls/juce_Slider.cpp +++ b/src/juce_appframework/gui/components/controls/juce_Slider.cpp @@ -128,6 +128,7 @@ Slider::Slider (const String& name) popupDisplayEnabled (false), menuEnabled (false), menuShown (false), + scrollWheelEnabled (true), valueBox (0), incButton (0), decButton (0), @@ -298,13 +299,13 @@ void Slider::hideTextBox (const bool discardCurrentEditorContents) } } -void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) +void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw() { sendChangeOnlyOnRelease = onlyNotifyOnRelease; } void Slider::setPopupDisplayEnabled (const bool enabled, - Component* const parentComponentToUse) + Component* const parentComponentToUse) throw() { popupDisplayEnabled = enabled; parentForPopupDisplay = parentComponentToUse; @@ -534,13 +535,13 @@ void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const b } void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled, - const double valueToSetOnDoubleClick) + const double valueToSetOnDoubleClick) throw() { doubleClickToValue = isDoubleClickEnabled; doubleClickReturnValue = valueToSetOnDoubleClick; } -double Slider::getDoubleClickReturnValue (bool& isEnabled_) const +double Slider::getDoubleClickReturnValue (bool& isEnabled_) const throw() { isEnabled_ = doubleClickToValue; return doubleClickReturnValue; @@ -622,11 +623,16 @@ void Slider::enablementChanged() repaint(); } -void Slider::setPopupMenuEnabled (const bool menuEnabled_) +void Slider::setPopupMenuEnabled (const bool menuEnabled_) throw() { menuEnabled = menuEnabled_; } +void Slider::setScrollWheelEnabled (const bool enabled) throw() +{ + scrollWheelEnabled = enabled; +} + //============================================================================== void Slider::labelTextChanged (Label* label) { @@ -1268,28 +1274,33 @@ void Slider::mouseDoubleClick (const MouseEvent&) } } -void Slider::mouseWheelMove (const MouseEvent&, float wheelIncrementX, float wheelIncrementY) +void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY) { - if (isEnabled() - && (maximum > minimum) - && ! isMouseButtonDownAnywhere()) + if (scrollWheelEnabled && isEnabled()) { - if (valueBox != 0) - valueBox->hideEditor (false); + if (maximum > minimum && ! isMouseButtonDownAnywhere()) + { + if (valueBox != 0) + valueBox->hideEditor (false); - const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; - const double currentPos = valueToProportionOfLength (currentValue); - const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); + const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f; + const double currentPos = valueToProportionOfLength (currentValue); + const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta)); - double delta = (newValue != currentValue) - ? jmax (fabs (newValue - currentValue), interval) : 0; + double delta = (newValue != currentValue) + ? jmax (fabs (newValue - currentValue), interval) : 0; - if (currentValue > newValue) - delta = -delta; + if (currentValue > newValue) + delta = -delta; - sendDragStart(); - setValue (snapValue (currentValue + delta, false), true, true); - sendDragEnd(); + sendDragStart(); + setValue (snapValue (currentValue + delta, false), true, true); + sendDragEnd(); + } + } + else + { + Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY); } } diff --git a/src/juce_appframework/gui/components/controls/juce_Slider.h b/src/juce_appframework/gui/components/controls/juce_Slider.h index bf0d9d7840..704be9a50b 100644 --- a/src/juce_appframework/gui/components/controls/juce_Slider.h +++ b/src/juce_appframework/gui/components/controls/juce_Slider.h @@ -427,7 +427,7 @@ public: @see getDoubleClickReturnValue */ void setDoubleClickReturnValue (const bool isDoubleClickEnabled, - const double valueToSetOnDoubleClick); + const double valueToSetOnDoubleClick) throw(); /** Returns the values last set by setDoubleClickReturnValue() method. @@ -436,7 +436,7 @@ public: @see setDoubleClickReturnValue */ - double getDoubleClickReturnValue (bool& isEnabled) const; + double getDoubleClickReturnValue (bool& isEnabled) const throw(); //============================================================================== /** Tells the slider whether to keep sending change messages while the user @@ -447,7 +447,7 @@ public: will be continuously sent as they drag it while the mouse button is still held down. */ - void setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease); + void setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease) throw(); /** If enabled, this gives the slider a pop-up bubble which appears while the slider is being dragged. @@ -462,7 +462,7 @@ public: you'll have to add it to a parent component instead). */ void setPopupDisplayEnabled (const bool isEnabled, - Component* const parentComponentToUse); + Component* const parentComponentToUse) throw(); /** If this is set to true, then right-clicking on the slider will pop-up a menu to let the user change the way it works. @@ -471,7 +471,13 @@ public: things like velocity sensitivity, and for rotary sliders, whether they use a linear or rotary mouse-drag to move them. */ - void setPopupMenuEnabled (const bool menuEnabled); + void setPopupMenuEnabled (const bool menuEnabled) throw(); + + /** This can be used to stop the mouse scroll-wheel from moving the slider. + + By default it's enabled. + */ + void setScrollWheelEnabled (const bool enabled) throw(); //============================================================================== /** Callback to indicate that the user is about to start dragging the slider. @@ -682,7 +688,7 @@ private: bool editableText : 1, doubleClickToValue : 1; bool isVelocityBased : 1, rotaryStop : 1, incDecButtonsSideBySide : 1; bool sendChangeOnlyOnRelease : 1, popupDisplayEnabled : 1; - bool menuEnabled : 1, menuShown : 1, mouseWasHidden : 1, incDecDragged : 1; + bool menuEnabled : 1, menuShown : 1, mouseWasHidden : 1, incDecDragged : 1, scrollWheelEnabled : 1; Font font; Label* valueBox; Button* incButton;