This is probably something silly; most of my mistakes are.
I ran into some surprise difficulty with Live 10 and parameter automation.
I can find the parameters, draw in automation, but then once I press play, that nasty orange box with the arrow (the Re-Enable Automation Button) tells me that something has changed. It does not, of course, tell me what exactly.
The code below replicates this problem in Live, though it works just fine in Reaper and Logic. My guess is that it has something to do with a (mis)use of AudioProcessorValueTreeState?
(Updated: One line in the editor.cpp should not have been there. Should not make a difference to those testing with this code.)
/*============Test PluginProcessor.h============*/
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
class TestAudioProcessor : public AudioProcessor
{
public:
TestAudioProcessor();
~TestAudioProcessor();
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
#endif
void processBlock (AudioBuffer<float>&, MidiBuffer&) override;
AudioProcessorEditor* createEditor() override;
bool hasEditor() const override;
const String getName() const override;
bool acceptsMidi() const override;
bool producesMidi() const override;
bool isMidiEffect() const override;
double getTailLengthSeconds() const override;
int getNumPrograms() override;
int getCurrentProgram() override;
void setCurrentProgram (int index) override;
const String getProgramName (int index) override;
void changeProgramName (int index, const String& newName) override;
void getStateInformation (MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
AudioProcessorValueTreeState* getParamValueTree() {return &tree;};
private:
juce::dsp::Gain<float> gain;
AudioProcessorValueTreeState tree;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestAudioProcessor)
};
–
/*============Test PluginProcessor.cpp============*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
TestAudioProcessor::TestAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
: AudioProcessor (BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput ("Input", AudioChannelSet::stereo(), true)
#endif
.withOutput ("Output", AudioChannelSet::stereo(), true)
#endif
)
#endif
, tree(*this, nullptr)
{
String pGain = "Gain";
tree.createAndAddParameter (pGain, "GainName", TRANS ("TransGain"),
NormalisableRange<float> (0.0f, 1.0f, 0.001f, 0.5, false), 0.5f,
[](float value) {return String(value * 100.f, 1);},
[](const String& text) {return text.getFloatValue() / 100.0f;},
false, true, false,
AudioProcessorParameter::genericParameter);
tree.state = ValueTree(String("ValueTreeState"));
}
TestAudioProcessor::~TestAudioProcessor(){}
const String TestAudioProcessor::getName() const
{
return JucePlugin_Name;
}
bool TestAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
return true;
#else
return false;
#endif
}
bool TestAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
return true;
#else
return false;
#endif
}
bool TestAudioProcessor::isMidiEffect() const
{
#if JucePlugin_IsMidiEffect
return true;
#else
return false;
#endif
}
double TestAudioProcessor::getTailLengthSeconds() const
{
return 0.0;
}
int TestAudioProcessor::getNumPrograms()
{
return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs,
// so this should be at least 1, even if you're not really implementing programs.
}
int TestAudioProcessor::getCurrentProgram()
{
return 0;
}
void TestAudioProcessor::setCurrentProgram (int index)
{
}
const String TestAudioProcessor::getProgramName (int index)
{
return {};
}
void TestAudioProcessor::changeProgramName (int index, const String& newName)
{
}
void TestAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
juce::dsp::ProcessSpec spec = { sampleRate,
static_cast<uint32>(samplesPerBlock),
static_cast<uint32>(this->getTotalNumOutputChannels()) };
gain.prepare(spec);
}
void TestAudioProcessor::releaseResources()
{
gain.reset();
}
#ifndef JucePlugin_PreferredChannelConfigurations
bool TestAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
#if JucePlugin_IsMidiEffect
ignoreUnused (layouts);
return true;
#else
// This is the place where you check if the layout is supported.
// In this template code we only support mono or stereo.
if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
return false;
// This checks if the input layout matches the output layout
#if ! JucePlugin_IsSynth
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;
#endif
return true;
#endif
}
#endif
void TestAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
ScopedNoDenormals noDenormals;
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear (i, 0, buffer.getNumSamples());
gain.setGainLinear(*tree.getRawParameterValue("Gain"));
dsp::AudioBlock<float> block (buffer);
const juce::dsp::ProcessContextReplacing<float>& context = block;
gain.process(context);
}
bool TestAudioProcessor::hasEditor() const
{
return true; // (change this to false if you choose to not supply an editor)
}
AudioProcessorEditor* TestAudioProcessor::createEditor()
{
return new TestAudioProcessorEditor (*this);
}
void TestAudioProcessor::getStateInformation (MemoryBlock& destData)
{
auto state = tree.copyState();
ScopedPointer<XmlElement> xml (state.createXml());
copyXmlToBinary (*xml, destData);
}
void TestAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
if (xmlState != nullptr) {
if (xmlState->hasTagName (tree.state.getType())) {
tree.replaceState (ValueTree::fromXml (*xmlState));
}
}
}
// This creates new instances of the plugin..
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new TestAudioProcessor();
}
–
/*============Test PluginEditor.h============*/
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginProcessor.h"
class TestAudioProcessorEditor : public AudioProcessorEditor, public Slider::Listener
{
public:
TestAudioProcessorEditor (TestAudioProcessor&);
~TestAudioProcessorEditor();
void paint (Graphics&) override;
void resized() override;
void sliderValueChanged (Slider* slider) override;
private:
TestAudioProcessor& processor;
std::unique_ptr<juce::Label> lblGain;
std::unique_ptr<juce::Slider> sldGain;
std::unique_ptr<AudioProcessorValueTreeState::SliderAttachment> sldAtt;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestAudioProcessorEditor)
};
–
/*============Test PluginEditor.cpp============*/
#include "PluginProcessor.h"
#include "PluginEditor.h"
TestAudioProcessorEditor::TestAudioProcessorEditor (TestAudioProcessor& p)
: AudioProcessorEditor (p), processor (p),
lblGain (new juce::Label ("lblGain", TRANS("Gain"))),
sldGain (new juce::Slider ("sldGain")),
sldAtt(new AudioProcessorValueTreeState::SliderAttachment (*p.getParamValueTree(), "Gain", *sldGain))
{
addAndMakeVisible (*lblGain);
lblGain->setEditable (false, false, false);
lblGain->setFont (Font (15.00f, Font::plain));
lblGain->setJustificationType (Justification::centredLeft);
lblGain->setColour (Label::textColourId, Colours::lightblue);
addAndMakeVisible(*sldGain);
sldGain->setRange (0., 1., 0.0001);
sldGain->setSliderStyle (Slider::RotaryVerticalDrag);
sldGain->setTextBoxStyle (Slider::NoTextBox, false, 80, 20);
sldGain->addListener(this);
setSize (400, 300);
}
TestAudioProcessorEditor::~TestAudioProcessorEditor (){}
void TestAudioProcessorEditor::paint (Graphics& g)
{
g.fillAll (Colours::blue);
g.setColour (Colours::red);
g.setFont (15.0f);
g.drawFittedText ("- - -", getLocalBounds(), Justification::centredBottom, 1);
}
void TestAudioProcessorEditor::resized()
{
lblGain->setBounds (29, 0, 149, 124);
sldGain->setBounds (22, 117, 149, 149);
}
void TestAudioProcessorEditor::sliderValueChanged (Slider* slider)
{
if (slider == sldGain.get()) {
// do something here, if you must
}
}