Hi all,
I am using the juce_dsp module to create a simple plugin with a simple GUI, involving processor chains. Specifically, the plugin has 2 oscillators with controls for setting frequency and amplitude. The GUI has 5 sliders (oscillator1’s frequency, oscillator1’s amplitude, oscillator2’s frequency, oscillator2’s amplitude, and a master gain). Here the screenshot.
The issue I am having is that when I control the gain of oscillator2 also the gain of oscillator1 changes accordingly. I really can’t spot where the issue is (of course no errors in variable names, I double checked it). I suspect that I am missing something at conceptual level, e.g., about how the processors relate to each other in the chain or about how the GUI relates to the processors’ parameters (also involving the value tree).
I would be immensely grateful to anybody who could explain me what I am doing wrong. I copy/paste the code here and also attach the corresponding 5 files (see the zip).
File MyCustomDspOscillator.h
#pragma once
//==============================================================================
template <typename Type>
class MyCustomDspOscillator
{
public:
//==============================================================================
MyCustomDspOscillator()
{
auto& osc = OscillatorProcessorChain.template get<oscIndex>();
int waveform = 1;
if (waveform == 0){
osc.initialise ([] (Type x)
{
return jmap (x, Type (-MathConstants<double>::pi), Type (MathConstants<double>::pi), Type (-1), Type (1));
}, 2);
}else
{
osc.initialise ([] (Type x) { return std::sin (x); }, 128);
}
osc.setFrequency(432.0f, false);
auto& gain = OscillatorProcessorChain.template get<gainIndex>();
gain.setGainLinear(0.015);
}
//==============================================================================
void setFrequency (Type newValue, bool force = false)
{
auto& osc = OscillatorProcessorChain.template get<oscIndex>();
osc.setFrequency (newValue, force);
}
//==============================================================================
void setLevel (Type newValue)
{
auto& gain = OscillatorProcessorChain.template get<gainIndex>();
gain.setGainLinear (newValue);
}
//==============================================================================
void reset() noexcept
{
OscillatorProcessorChain.reset();
}
//==============================================================================
template <typename ProcessContext>
void process (const ProcessContext& context) noexcept
{
OscillatorProcessorChain.process (context);
}
//==============================================================================
void prepare (const juce::dsp::ProcessSpec& spec)
{
OscillatorProcessorChain.prepare (spec);
}
private:
//==============================================================================
enum
{
oscIndex,
gainIndex
};
juce::dsp::ProcessorChain<juce::dsp::Oscillator<Type>, juce::dsp::Gain<Type>> OscillatorProcessorChain;
};
File PuginProcessor.h
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "MyCustomDspOscillator.h"
//==============================================================================
/**
*/
class MyDspOscillatorGeneratorAudioProcessor : public AudioProcessor
{
public:
//==============================================================================
MyDspOscillatorGeneratorAudioProcessor();
~MyDspOscillatorGeneratorAudioProcessor();
//==============================================================================
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;
//==============================================================================
void updateParameters();
void process(dsp::ProcessContextReplacing<float> context);
void reset() override;
private:
//==============================================================================
AudioProcessorValueTreeState parameters;
std::atomic<float>* osc1GainParameter = nullptr;
std::atomic<float>* osc1FreqParameter = nullptr;
std::atomic<float>* osc2GainParameter = nullptr;
std::atomic<float>* osc2FreqParameter = nullptr;
std::atomic<float>* globalGainParameter = nullptr;
enum
{
osc1Index,
osc2Index,
masterGainIndex
};
juce::dsp::ProcessorChain<MyCustomDspOscillator<float>, MyCustomDspOscillator<float>, juce::dsp::Gain<float>> processorChain;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyDspOscillatorGeneratorAudioProcessor)
};
File PluginProcessor.cpp
#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
MyDspOscillatorGeneratorAudioProcessor::MyDspOscillatorGeneratorAudioProcessor()
: AudioProcessor (BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true)),
parameters (*this, nullptr, Identifier ("MyDspOscillatorGenerator"),
{
std::make_unique<AudioParameterFloat> ("osc1_gain", // parameterID
"Osc1 Gain", // parameter name
0.0f, // minimum value
1.0f, // maximum value
0.5f), // default value
std::make_unique<AudioParameterFloat> ("osc1_freq", // parameterID
"Osc1 Freq", // parameter name
100.0f, // minimum value
5000.0f, // maximum value
432.0f), // default value
std::make_unique<AudioParameterFloat> ("osc2_gain", // parameterID
"Osc2 Gain", // parameter name
0.0f, // minimum value
1.0f, // maximum value
0.5f), // default value
std::make_unique<AudioParameterFloat> ("osc2_freq", // parameterID
"Osc2 Freq", // parameter name
100.0f, // minimum value
5000.0f, // maximum value
432.0f), // default value
std::make_unique<AudioParameterFloat> ("global_gain", // parameterID
"Global Gain", // parameter name
0.0f, // minimum value
1.0f, // maximum value
0.5f) // default value
})
{
osc1GainParameter = parameters.getRawParameterValue ("osc1_gain");
osc1FreqParameter = parameters.getRawParameterValue ("osc1_freq");
osc2GainParameter = parameters.getRawParameterValue ("osc2_gain");
osc2FreqParameter = parameters.getRawParameterValue ("osc2_freq");
globalGainParameter = parameters.getRawParameterValue ("global_gain");
}
MyDspOscillatorGeneratorAudioProcessor::~MyDspOscillatorGeneratorAudioProcessor()
{
}
//==============================================================================
const String MyDspOscillatorGeneratorAudioProcessor::getName() const
{
return JucePlugin_Name;
}
bool MyDspOscillatorGeneratorAudioProcessor::acceptsMidi() const
{
#if JucePlugin_WantsMidiInput
return true;
#else
return false;
#endif
}
bool MyDspOscillatorGeneratorAudioProcessor::producesMidi() const
{
#if JucePlugin_ProducesMidiOutput
return true;
#else
return false;
#endif
}
bool MyDspOscillatorGeneratorAudioProcessor::isMidiEffect() const
{
#if JucePlugin_IsMidiEffect
return true;
#else
return false;
#endif
}
double MyDspOscillatorGeneratorAudioProcessor::getTailLengthSeconds() const
{
return 0.0;
}
int MyDspOscillatorGeneratorAudioProcessor::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 MyDspOscillatorGeneratorAudioProcessor::getCurrentProgram()
{
return 0;
}
void MyDspOscillatorGeneratorAudioProcessor::setCurrentProgram (int index)
{
}
const String MyDspOscillatorGeneratorAudioProcessor::getProgramName (int index)
{
return {};
}
void MyDspOscillatorGeneratorAudioProcessor::changeProgramName (int index, const String& newName)
{
}
//==============================================================================
void MyDspOscillatorGeneratorAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
auto channels = static_cast<uint32> (jmin (getMainBusNumInputChannels(), getMainBusNumOutputChannels()));
dsp::ProcessSpec spec { sampleRate, static_cast<uint32> (samplesPerBlock), channels };
processorChain.prepare (spec);
updateParameters();
reset();
}
void MyDspOscillatorGeneratorAudioProcessor::releaseResources()
{
// When playback stops, you can use this as an opportunity to free up any
// spare memory, etc.
}
bool MyDspOscillatorGeneratorAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
if (layouts.getMainOutputChannelSet() == AudioChannelSet::disabled())
return false;
if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
return false;
return true;
}
void MyDspOscillatorGeneratorAudioProcessor::updateParameters()
{
//Update your parameters for your processes
float osc1_freqHz = *osc1FreqParameter;
float osc2_freqHz = *osc2FreqParameter;
float osc1_amplitiude = *osc1GainParameter;
float osc2_amplitiude = *osc2GainParameter;
float global_amplitude = *globalGainParameter;;
processorChain.get<osc1Index>().setFrequency (osc1_freqHz, true);
processorChain.get<osc1Index>().setLevel (osc1_amplitiude);
processorChain.get<osc2Index>().setFrequency (osc2_freqHz, true);
processorChain.get<osc2Index>().setLevel (osc2_amplitiude);
processorChain.get<masterGainIndex>().setGainLinear (global_amplitude);
}
void MyDspOscillatorGeneratorAudioProcessor::process(dsp::ProcessContextReplacing<float> context)
{
processorChain.process (context);
}
void MyDspOscillatorGeneratorAudioProcessor::reset()
{
processorChain.reset();
}
void MyDspOscillatorGeneratorAudioProcessor::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());
updateParameters();
dsp::AudioBlock<float> block (buffer);
process(dsp::ProcessContextReplacing<float> (block));
}
//==============================================================================
bool MyDspOscillatorGeneratorAudioProcessor::hasEditor() const
{
return true; // (change this to false if you choose to not supply an editor)
}
AudioProcessorEditor* MyDspOscillatorGeneratorAudioProcessor::createEditor()
{
return new MyDspOscillatorGeneratorAudioProcessorEditor (*this, parameters);
}
//==============================================================================
void MyDspOscillatorGeneratorAudioProcessor::getStateInformation (MemoryBlock& destData)
{
auto state = parameters.copyState();
std::unique_ptr<XmlElement> xml (state.createXml());
copyXmlToBinary (*xml, destData);
}
void MyDspOscillatorGeneratorAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
std::unique_ptr<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
if (xmlState.get() != nullptr)
if (xmlState->hasTagName (parameters.state.getType()))
parameters.replaceState (ValueTree::fromXml (*xmlState));
}
//==============================================================================
// This creates new instances of the plugin..
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new MyDspOscillatorGeneratorAudioProcessor();
}
File PluginEditor.h
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginProcessor.h"
//==============================================================================
class MyDspOscillatorGeneratorAudioProcessorEditor : public AudioProcessorEditor
{
public:
enum
{
paramControlHeight = 40,
paramLabelWidth = 80,
paramSliderWidth = 350
};
typedef AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
MyDspOscillatorGeneratorAudioProcessorEditor (MyDspOscillatorGeneratorAudioProcessor& parent, AudioProcessorValueTreeState& vts);
~MyDspOscillatorGeneratorAudioProcessorEditor();
//==============================================================================
void paint (Graphics&) override;
void resized() override;
private:
AudioProcessorValueTreeState& valueTreeState;
Label osc1GainLabel;
Label osc1FreqLabel;
Slider osc1GainSlider;
Slider osc1FreqSlider;
Label osc2GainLabel;
Label osc2FreqLabel;
Slider osc2GainSlider;
Slider osc2FreqSlider;
Label globalGainLabel;
Slider globalGainSlider;
std::unique_ptr<SliderAttachment> osc1GainAttachment;
std::unique_ptr<SliderAttachment> osc1FreqAttachment;
std::unique_ptr<SliderAttachment> osc2GainAttachment;
std::unique_ptr<SliderAttachment> osc2FreqAttachment;
std::unique_ptr<SliderAttachment> globalGainAttachment;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyDspOscillatorGeneratorAudioProcessorEditor)
};
File PluginEditor.cpp
#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
MyDspOscillatorGeneratorAudioProcessorEditor::MyDspOscillatorGeneratorAudioProcessorEditor (MyDspOscillatorGeneratorAudioProcessor& p, AudioProcessorValueTreeState& vts)
: AudioProcessorEditor (&p), valueTreeState (vts)
{
osc1GainLabel.setText ("Gain osc1", dontSendNotification);
osc1GainLabel.attachToComponent (&osc1GainSlider, true);
addAndMakeVisible (osc1GainLabel);
osc1FreqLabel.setText ("Freq osc1", dontSendNotification);
osc1FreqLabel.attachToComponent (&osc1FreqSlider, true);
addAndMakeVisible (osc1FreqLabel);
getLookAndFeel().setColour (Slider::thumbColourId, Colours::red);
addAndMakeVisible (osc1GainSlider);
osc1FreqSlider.setTextValueSuffix (" Hz");
addAndMakeVisible (osc1FreqSlider);
osc1GainAttachment.reset (new SliderAttachment (valueTreeState, "osc1_gain", osc1GainSlider));
osc1FreqAttachment.reset (new SliderAttachment (valueTreeState, "osc1_freq", osc1FreqSlider));
osc2GainLabel.setText ("Gain osc2", dontSendNotification);
osc2GainLabel.attachToComponent (&osc2GainSlider, true);
addAndMakeVisible (osc2GainLabel);
osc2FreqLabel.setText ("Freq osc2", dontSendNotification);
osc2FreqLabel.attachToComponent (&osc2FreqSlider, true);
addAndMakeVisible (osc2FreqLabel);
getLookAndFeel().setColour (Slider::thumbColourId, Colours::red);
addAndMakeVisible (osc2GainSlider);
osc2FreqSlider.setTextValueSuffix (" Hz");
addAndMakeVisible (osc2FreqSlider);
osc2GainAttachment.reset (new SliderAttachment (valueTreeState, "osc2_gain", osc2GainSlider));
osc2FreqAttachment.reset (new SliderAttachment (valueTreeState, "osc2_freq", osc2FreqSlider));
globalGainLabel.setText ("Global Gain", dontSendNotification);
globalGainLabel.attachToComponent (&globalGainSlider, true);
addAndMakeVisible (globalGainLabel);
getLookAndFeel().setColour (Slider::thumbColourId, Colours::red);
addAndMakeVisible (globalGainSlider);
globalGainAttachment.reset (new SliderAttachment (valueTreeState, "global_gain", globalGainSlider));
setSize (paramSliderWidth + paramLabelWidth + 50, jmax (500, paramControlHeight * 2 + 20));
}
MyDspOscillatorGeneratorAudioProcessorEditor::~MyDspOscillatorGeneratorAudioProcessorEditor()
{
}
//==============================================================================
void MyDspOscillatorGeneratorAudioProcessorEditor::paint (Graphics& g)
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void MyDspOscillatorGeneratorAudioProcessorEditor::resized()
{
auto sliderLeft = paramLabelWidth + 10;
osc1GainSlider.setBounds (sliderLeft, 20, getWidth() - sliderLeft - 10, 20);
osc1FreqSlider.setBounds (sliderLeft, 50, getWidth() - sliderLeft - 10, 20);
osc2GainSlider.setBounds (sliderLeft, 80, getWidth() - sliderLeft - 10, 20);
osc2FreqSlider.setBounds (sliderLeft, 110, getWidth() - sliderLeft - 10, 20);
globalGainSlider.setBounds (sliderLeft, 140, getWidth() - sliderLeft - 10, 20);
}
Source.zip (7.7 KB)