ToggleButton and setParameterNotifyingHost not working


#1

I have this really odd issue, which I believe is due to me overlooking something very very basic.

When I click a button on my AU plugin, it fails to notify the host (Logic or Waveburner), and the project does not update to a ‘dirty’ state.

While I’m still trying to debug the whole thing, I have managed to create a simple plugin (via Introjucer) that demonstrates the issue. It works as expected with Logic, but not with Waveburner.

It is worth noting that JuceDemoPlugin works as expected with both Logic and Waveburner. So for the life of me, I cannot understand why the following code doesn’t work with Waveburnerl:

void BtnTestAudioProcessorEditor::buttonClicked (Button* aButton)
{
    BtnTestAudioProcessor* iProcessor = getProcessor();    
    iProcessor->beginParameterChangeGesture(BtnTestAudioProcessor::isMonoParam);    
    float iVal = aButton->getToggleState() ? 1.0f : 0.0f;
    DBG(iVal);
    iProcessor->setParameterNotifyingHost (BtnTestAudioProcessor::isMonoParam, iVal);
    iProcessor->endParameterChangeGesture  (BtnTestAudioProcessor::isMonoParam);
}

By the way commenting the Gesture method does not solve the issue.

My system:
[list]
[]Juce: 1.53.107 and 1.54.27 (git) tested[/]
[] Mac: 10.7.2[/]
[] XCode: 4.2.1[/]
[] Logic: 9.1.6[/]
[] Waveburner: 1.6.1[/][/list]

Here is the full code of the plugin:

PluginEditor.h:

#ifndef __PLUGINEDITOR_H_325555FB__
#define __PLUGINEDITOR_H_325555FB__

#include "../JuceLibraryCode/JuceHeader.h"
#include "../JuceLibraryCode/JucePluginCharacteristics.h"
#include "PluginProcessor.h"

class BtnTestAudioProcessorEditor  : public AudioProcessorEditor,
                                     public ButtonListener
{
public:
    BtnTestAudioProcessorEditor (BtnTestAudioProcessor* ownerFilter);
    ~BtnTestAudioProcessorEditor();
    
    void buttonClicked (Button* buttonThatWasClicked);

    void paint (Graphics& g);
private:
    ToggleButton monoBtn;
    
    BtnTestAudioProcessor* getProcessor() const
    {
        return static_cast <BtnTestAudioProcessor*> (getAudioProcessor());
    }    
};


#endif  // __PLUGINEDITOR_H_325555FB__

PluginEditor.cpp:

#include "PluginProcessor.h"
#include "PluginEditor.h"

BtnTestAudioProcessorEditor::BtnTestAudioProcessorEditor (BtnTestAudioProcessor* ownerFilter)
    : AudioProcessorEditor (ownerFilter),
      monoBtn ("Mono")
{
    addAndMakeVisible(&monoBtn);
    monoBtn.setBounds(10, 10, 100, 20);
    monoBtn.addListener(this);
    
    setSize (400, 300);
}

BtnTestAudioProcessorEditor::~BtnTestAudioProcessorEditor()
{
}

void BtnTestAudioProcessorEditor::paint (Graphics& g)
{
    g.fillAll (Colours::white);
}

void BtnTestAudioProcessorEditor::buttonClicked (Button* buttonThatWasClicked)
{
    BtnTestAudioProcessor* iProcessor = getProcessor();    
    iProcessor->beginParameterChangeGesture(BtnTestAudioProcessor::isMonoParam);    
    float iVal = monoBtn.getToggleState() ? 1.0f : 0.0f;
    DBG(iVal);
    iProcessor->setParameterNotifyingHost (BtnTestAudioProcessor::isMonoParam, iVal);
    iProcessor->endParameterChangeGesture  (BtnTestAudioProcessor::isMonoParam);
}

PluginProcessor.h:

/*
  ==============================================================================

    This file was auto-generated by the Jucer!

    It contains the basic startup code for a Juce application.

  ==============================================================================
*/

#ifndef __PLUGINPROCESSOR_H_A2A6DFB8__
#define __PLUGINPROCESSOR_H_A2A6DFB8__

#include "../JuceLibraryCode/JuceHeader.h"
#include "../JuceLibraryCode/JucePluginCharacteristics.h"


//==============================================================================
/**
*/
class BtnTestAudioProcessor  : public AudioProcessor
{
public:
    //==============================================================================
    BtnTestAudioProcessor();
    ~BtnTestAudioProcessor();

    //==============================================================================
    void prepareToPlay (double sampleRate, int samplesPerBlock);
    void releaseResources();

    void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages);

    //==============================================================================
    AudioProcessorEditor* createEditor();
    bool hasEditor() const;

    //==============================================================================
    const String getName() const;

    int getNumParameters();

    float getParameter (int index);
    void setParameter (int index, float newValue);

    const String getParameterName (int index);
    const String getParameterText (int index);

    const String getInputChannelName (int channelIndex) const;
    const String getOutputChannelName (int channelIndex) const;
    bool isInputChannelStereoPair (int index) const;
    bool isOutputChannelStereoPair (int index) const;

    bool acceptsMidi() const;
    bool producesMidi() const;

    //==============================================================================
    int getNumPrograms();
    int getCurrentProgram();
    void setCurrentProgram (int index);
    const String getProgramName (int index);
    void changeProgramName (int index, const String& newName);

    //==============================================================================
    void getStateInformation (MemoryBlock& destData);
    void setStateInformation (const void* data, int sizeInBytes);

    enum Parameters
    {
        isMonoParam = 0,
        
        totalNumParams
    };
    
    float isMono;    
    
private:
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BtnTestAudioProcessor);
};

#endif  // __PLUGINPROCESSOR_H_A2A6DFB8__

PluginProcessor.cpp:

/*
  ==============================================================================

    This file was auto-generated by the Jucer!

    It contains the basic startup code for a Juce application.

  ==============================================================================
*/

#include "PluginProcessor.h"
#include "PluginEditor.h"


//==============================================================================
BtnTestAudioProcessor::BtnTestAudioProcessor()
{
    isMono = 0.0f;
}

BtnTestAudioProcessor::~BtnTestAudioProcessor()
{
}

//==============================================================================
const String BtnTestAudioProcessor::getName() const
{
    return JucePlugin_Name;
}

int BtnTestAudioProcessor::getNumParameters()
{
    return totalNumParams;
}

float BtnTestAudioProcessor::getParameter (int index)
{
    return isMono;
}

void BtnTestAudioProcessor::setParameter (int index, float newValue)
{
    isMono = newValue;
}

const String BtnTestAudioProcessor::getParameterName (int index)
{
    return "Mono";
}

const String BtnTestAudioProcessor::getParameterText (int index)
{
    return isMono == 0.0f ? "Mono" : "Stereo";
}

const String BtnTestAudioProcessor::getInputChannelName (int channelIndex) const
{
    return String (channelIndex + 1);
}

const String BtnTestAudioProcessor::getOutputChannelName (int channelIndex) const
{
    return String (channelIndex + 1);
}

bool BtnTestAudioProcessor::isInputChannelStereoPair (int index) const
{
    return true;
}

bool BtnTestAudioProcessor::isOutputChannelStereoPair (int index) const
{
    return true;
}

bool BtnTestAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
    return true;
#else
    return false;
#endif
}

bool BtnTestAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
    return true;
#else
    return false;
#endif
}

int BtnTestAudioProcessor::getNumPrograms()
{
    return 0;
}

int BtnTestAudioProcessor::getCurrentProgram()
{
    return 0;
}

void BtnTestAudioProcessor::setCurrentProgram (int index)
{
}

const String BtnTestAudioProcessor::getProgramName (int index)
{
    return String::empty;
}

void BtnTestAudioProcessor::changeProgramName (int index, const String& newName)
{
}

//==============================================================================
void BtnTestAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
}

void BtnTestAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
}

void BtnTestAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    // This is the place where you'd normally do the guts of your plugin's
    // audio processing...
    for (int channel = 0; channel < getNumInputChannels(); ++channel)
    {
        float* channelData = buffer.getSampleData (channel);

        // ..do something to the data...
    }

    // In case we have more outputs than inputs, we'll clear any output
    // channels that didn't contain input data, (because these aren't
    // guaranteed to be empty - they may contain garbage).
    for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
    {
        buffer.clear (i, 0, buffer.getNumSamples());
    }
}

//==============================================================================
bool BtnTestAudioProcessor::hasEditor() const
{
    return true; // (change this to false if you choose to not supply an editor)
}

AudioProcessorEditor* BtnTestAudioProcessor::createEditor()
{
    return new BtnTestAudioProcessorEditor (this);
}

//==============================================================================
void BtnTestAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
}

void BtnTestAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
}

//==============================================================================
// This creates new instances of the plugin..
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new BtnTestAudioProcessor();
}

=====================================================================================================

=====================================================================================================

Edit:

OK, I’ve done some extra debugging into this, and although it might confuse some, it might shade some light on the cause of the problem.

I’ve added a slider, and the slider changes the same parameter as the ToggleButton.

In logic, everything works as expected, that is:
[list]
[] When one moves the slider, the document immediately becomes dirty.[/]
[] When one clicks on the label and edits the value manually, the moment Enter is pressed the document becomes dirty.[/][/list]

Now in Waveburner, the behaviour is strange, and now my test plugin behaves like JuceDemoPlugin (which shows the same wired behaviour):

[list]
[] When one moves the slider, the document doesn’t become dirty, until one release the mouse.[/]
[] When one edit the label, nothing happens.[/][/list]

Now my question is, what might cause Waveburner to go dirty when one lifts the mouse (endDrag) on the slider?

To see this, here are the new files:

PluginEditor.h:

#ifndef __PLUGINEDITOR_H_325555FB__
#define __PLUGINEDITOR_H_325555FB__

#include "../JuceLibraryCode/JuceHeader.h"
#include "../JuceLibraryCode/JucePluginCharacteristics.h"
#include "PluginProcessor.h"

class BtnTestAudioProcessorEditor  : public AudioProcessorEditor,
                                     public ButtonListener,
                                     public SliderListener
{
public:
    BtnTestAudioProcessorEditor (BtnTestAudioProcessor* ownerFilter);
    ~BtnTestAudioProcessorEditor();
    
    void buttonClicked (Button* aButton);
    void sliderValueChanged (Slider* aSlide);    

    void paint (Graphics& g);
private:
    ToggleButton monoBtn;
    Slider gainSlider;
    
    BtnTestAudioProcessor* getProcessor() const
    {
        return static_cast <BtnTestAudioProcessor*> (getAudioProcessor());
    }    
};


#endif  // __PLUGINEDITOR_H_325555FB__

PluginEditor.cpp:

#include "PluginProcessor.h"
#include "PluginEditor.h"

BtnTestAudioProcessorEditor::BtnTestAudioProcessorEditor (BtnTestAudioProcessor* ownerFilter)
    : AudioProcessorEditor (ownerFilter),
      monoBtn (T("Mono")),
      gainSlider("gain")
{
    addAndMakeVisible(&monoBtn);
    monoBtn.setBounds(10, 10, 100, 20);
    monoBtn.addListener(this);

    addAndMakeVisible (&gainSlider);
    gainSlider.setSliderStyle (Slider::Rotary);
    gainSlider.addListener (this);
    gainSlider.setRange (0.0, 1.0, 0.01);    
    gainSlider.setBounds(10,50,100,20);
    setSize (400, 300);
}

BtnTestAudioProcessorEditor::~BtnTestAudioProcessorEditor()
{
}

void BtnTestAudioProcessorEditor::paint (Graphics& g)
{
    g.fillAll (Colours::white);
}

void BtnTestAudioProcessorEditor::buttonClicked (Button* aButton)
{
    BtnTestAudioProcessor* iProcessor = getProcessor();    
    iProcessor->beginParameterChangeGesture(BtnTestAudioProcessor::isMonoParam);    
    float iVal = monoBtn.getToggleState() ? 1.0f : 0.0f;
    DBG(iVal);
    iProcessor->setParameterNotifyingHost (BtnTestAudioProcessor::isMonoParam, iVal);
    iProcessor->endParameterChangeGesture  (BtnTestAudioProcessor::isMonoParam);
}

void BtnTestAudioProcessorEditor::sliderValueChanged (Slider* aSlide)
{
    BtnTestAudioProcessor* iProcessor = getProcessor();
    iProcessor->beginParameterChangeGesture(BtnTestAudioProcessor::isMonoParam);    
    float iVal = (float) gainSlider.getValue();
    DBG(iVal);
    iProcessor->setParameterNotifyingHost (BtnTestAudioProcessor::isMonoParam, iVal);
    iProcessor->endParameterChangeGesture  (BtnTestAudioProcessor::isMonoParam);    
}

#2

OK,

I’ve found out that if we subclass Button from AsyncUpdater, and if the call to setParameterNotifyingHost is done from within the call stack of handleAsyncUpdate (like with slider), WaveBurner will be marked dirty correctly.

Can anyone speculate why would this would make a difference?

And as a general question, why Slider is AsyncUpdater and Button not?

Thanks


#3

There’s probably a recursive call going on if you try to invoke it synchronously?

In a button, you want one callback for each time it gets pressed. But dragging a slider generates thousands of movement events, and you definitely don’t want a callback for every one of them!


#4

I’m not sure what you mean by that (could you elaborate); and is it my code (above) causing this or WaveBurner?


#5

If your code sends a change message to WB, and that calls back to tell you there’s a change, and in response to that you tell WB that there’s a change, etc etc.


#6

Nope, pretty sure I’m not doing this. I have setParameterNotifyingHost upon buttonClicked event, then the setParameter is simply:

void BtnTestAudioProcessor::setParameter (int index, float newValue)
{
    isMono = newValue;
}

#7

Yeah, I’m not saying that it’s your code that’s doing it, but the wrapper code could be. Obviously that doesn’t happen with most hosts, but maybe WaveBurner is doing something odd.