void HRTFPluginV2AudioProcessor::updateGraph() {
bool hasChanged = false;
juce::Array<juce::AudioParameterChoice*> choices {processorSlot1.get(),
processorSlot2.get(),
processorSlot3.get(),
};
juce::Array<juce::AudioParameterBool*> bypasses { bypassSlot1.get(),
bypassSlot2.get(),
bypassSlot3.get(),
};
juce::ReferenceCountedArray<Node> slots;
slots.add(slot1Node);
slots.add(slot2Node);
slots.add(slot3Node);
for (int i = 0; i < 3; ++i) {
std::cout << "for loop idx: " << i << std::endl;
auto& choice = choices.getReference (i);
auto slot = slots .getUnchecked (i);
std::cout << "choice index: " << choice->getIndex() << std::endl;
if (choice->getIndex() == 0)
{
std::cout << "choice is empty" << std::endl;
if (slot != nullptr) {
mainProcessor->removeNode (slot.get());
slots.set (i, nullptr);
hasChanged = true;
}
}
else if (choice->getIndex() == 1)
{
std::cout << "choice->getIndex() == 1" << std::endl;
if (slot != nullptr) {
if (slot->getProcessor()->getName() == "HRTFProcessor")
{
std::cout << "choice is HRTFProcessor" << std::endl;
continue;
}
mainProcessor->removeNode (slot.get());
}
std::cout << "Making new HRTFProcessor" << std::endl;
slots.set (i, mainProcessor->addNode (std::make_unique<HRTFProcessor>()));
hasChanged = true;
}
else if (choice->getIndex() == 2) {
std::cout << "choice->getIndex() == 2" << std::endl;
if (slot != nullptr) {
if (slot->getProcessor()->getName() == "EarlyReflections"){
std::cout << "choice is EarlyReflections" << std::endl;
continue;
}
mainProcessor->removeNode (slot.get());
}
std::cout << "Making new EarlyReflections" << std::endl;
slots.set (i, mainProcessor->addNode (std::make_unique<EarlyReflections>()));
hasChanged = true;
}
else if (choice->getIndex() == 3) {
std::cout << "choice->getIndex() == 3" << std::endl;
if (slot != nullptr) {
if (slot->getProcessor()->getName() == "AirAbsorption"){
std::cout << "choice is AirAbsorption" << std::endl;
continue;
}
mainProcessor->removeNode (slot.get());
}
std::cout << "Making new AirAbsorption" << std::endl;
slots.set (i, mainProcessor->addNode (std::make_unique<AirAbsorption>()));
hasChanged = true;
}
}
// If the graph has changed, we'll need to re-connect the nodes.
if (hasChanged) {
std::cout << "in hasChanged" << std::endl;
for (auto connection : mainProcessor->getConnections()){ //remove all connections
mainProcessor->removeConnection (connection);
std::cout << "Removed a Connection" << std::endl;
}
juce::ReferenceCountedArray<Node> activeSlots;
for (auto slot : slots) {
if (slot != nullptr) {
activeSlots.add (slot);
slot->getProcessor()->setPlayConfigDetails (getMainBusNumInputChannels(),
getMainBusNumOutputChannels(),
getSampleRate(), getBlockSize());
}
}
if (activeSlots.isEmpty()) { //if no active slots, connect input to output
std::cout << "activeSlots is empty" << std::endl;
connectAudioNodes();
}
else
{
std::cout << "activeSlots is not empty" << std::endl;
for (int i = 0; i < activeSlots.size() - 1; ++i) { //connect slots in series
for (int channel = 0; channel < 2; ++channel) {
mainProcessor->addConnection ({ { activeSlots.getUnchecked (i)->nodeID, channel },
{ activeSlots.getUnchecked (i + 1)->nodeID, channel } });
}
}
for (int channel = 0; channel < 2; ++channel) { //connect input and output to first and last slots
mainProcessor->addConnection ({ { audioInputNode->nodeID, channel },
{ activeSlots.getFirst()->nodeID, channel } });
mainProcessor->addConnection ({ { activeSlots.getLast()->nodeID, channel },
{ audioOutputNode->nodeID, channel } });
}
}
connectMidiNodes();
for (auto node : mainProcessor->getNodes()) { //enable all buses
node->getProcessor()->enableAllBuses();
}
//deal with bypass state
for (int i = 0; i < 3; ++i) {
auto slot = slots .getUnchecked (i);
auto& bypass = bypasses.getReference (i);
if (slot != nullptr){
std::cout << "slot is not nullptr, set the bypass" << std::endl;
slot->setBypassed (bypass->get());
}
}
audioInputNode->setBypassed (muteInput->get());
slot1Node = slots.getUnchecked (0);
slot2Node = slots.getUnchecked (1);
slot3Node = slots.getUnchecked (2);
}
}
I use this updateGraph() function as per the CascasdingPlugins tutorial code, However, no matter what processor I select, from the print statements I can see that index 3 is always being selected as true, thus it is creating an AirAbsorption plugin, regardless of the selection.
class HRTFPluginV2AudioProcessorEditor : public juce::AudioProcessorEditor,
// public juce::Slider::Listener,
public juce::ComboBox::Listener,
public juce::Button::Listener
{
public:
HRTFPluginV2AudioProcessorEditor (HRTFPluginV2AudioProcessor&, juce::AudioProcessorValueTreeState& vts);
~HRTFPluginV2AudioProcessorEditor() override;
juce::String getImpResFileName();
juce::String fileName;
//====SETTERS=====
void setAirTemperature();
void setEarlyReflections();
void setHumidity();
void setAirPressure();
void setDistance();
void setAzimuth();
void setElevation();
void setMaxDelayTime();
void setDelayTime1();
void setDelayTime2();
void setFeedback();
void setWetLevel();
void setImpResFileName();
//========================================================================
//For adding the sliders to the userParameters object
typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
typedef juce::AudioProcessorValueTreeState::ButtonAttachment ButtonAttachment;
//Do not need slider Listener any more as value tree handles the Listeners
//=============Slider Listener Override from base class================
// void sliderValueChanged(juce::Slider* slider) override
// {}
//=============ComboBox Listener Override=========================
void comboBoxChanged(juce::ComboBox* comboBox)
{
int processorIndex = comboBox->getSelectedItemIndex();
selectProcessor(comboBox, processorIndex);
}
void selectProcessor(juce::ComboBox* box, int choiceIndex)
{
// Define a pointer to the processor slot that needs to be updated based on the ComboBox
juce::AudioParameterChoice* targetSlot = nullptr;
if (box == &processorBox1)
{
targetSlot = audioProcessor.processorSlot1.get();
}
else if (box == &processorBox2)
{
targetSlot = audioProcessor.processorSlot2.get();
}
else if (box == &processorBox3)
{
targetSlot = audioProcessor.processorSlot3.get();
}
// Ensure the targetSlot is valid before using it
if (!targetSlot) return;
targetSlot->setValueNotifyingHost(choiceIndex);
}
//=============Button Listener Overrides=========================
void buttonStateChanged (juce::Button * button) override
{
}
void buttonClicked(juce::Button* button) override
{
if (button == &bypassButton1)
{
audioProcessor.bypassSlot1->beginChangeGesture(); // Begin parameter change
*audioProcessor.bypassSlot1 = button->getToggleState(); // Set bypass state based on button's state
audioProcessor.bypassSlot1->endChangeGesture(); // End parameter change
}
else if (button == &bypassButton2)
{
audioProcessor.bypassSlot2->beginChangeGesture(); // Begin parameter change
*audioProcessor.bypassSlot2 = button->getToggleState(); // Set bypass state based on button's state
audioProcessor.bypassSlot2->endChangeGesture(); // End parameter change
}
else if (button == &bypassButton3)
{
audioProcessor.bypassSlot3->beginChangeGesture();
*audioProcessor.bypassSlot3 = button->getToggleState();
audioProcessor.bypassSlot3->endChangeGesture();
}
// Call updateGraph to reflect the changes in the graph
audioProcessor.updateGraph();
}
void paint (juce::Graphics&) override;
void resized() override;
private:
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
HRTFPluginV2AudioProcessor& audioProcessor;
std::unique_ptr<EarlyReflections> earlyReflections;
std::unique_ptr<HRTFProcessor> hrtfProcessor;
std::unique_ptr<AirAbsorption> airAbsorption;
//Title text box
juce::TextButton titleComponent;
//=============Sliders================
//--HRTF--
juce::Slider azimuthSlider;
juce::Label azimuthLabel;
juce::Slider elevationSlider;
juce::Label elevationLabel;
//--AirAbsorption--
juce::Slider airTemperatureSlider;
juce::Label airTemperatureLabel;
juce::Slider humiditySlider;
juce::Label humidityLabel;
juce::Slider airPressureSlider;
juce::Label airPressureLabel;
juce::Slider distanceSlider;
juce::Label distanceLabel;
//--EarlyReflections/Reverb--
juce::Slider earlyReflectionTimeSlider;
juce::Label earlyReflectionTimeLabel;
juce::Slider maxDelayTimeSlider;
juce::Label maxDelayTimeLabel;
juce::Slider delayTime1Slider;
juce::Label delayTime1Label;
juce::Slider delayTime2Slider;
juce::Label delayTime2Label;
juce::Slider feedbackSlider;
juce::Label feedbackLabel;
juce::Slider wetLevelSlider;
juce::Label wetLevelLabel;
//=========Attachment Pointers for putting Sliders in the ValueTree================
//Ptr to the valueTreeState
juce::AudioProcessorValueTreeState& valueTreeState;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> azimuthSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> elevationSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> airTemperatureSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> humiditySliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> airPressureSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> distanceSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> earlyReflectionTimeSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> maxDelayTimeSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> delayTime1SliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> delayTime2SliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> feedbackSliderPtr;
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> wetLevelSliderPtr;
//===Look and Feel===
std::unique_ptr<juce::LookAndFeel_V4> HRTFLook;
std::unique_ptr<juce::LookAndFeel_V4> airAbsorptionLook;
std::unique_ptr<juce::LookAndFeel_V4> earlyReflectionsLook;
//==Impulse Response Stuff==//
juce::TextButton loadBtn;
std::unique_ptr<juce::FileChooser> fileChooser;
juce::Label IRname;
juce::String impResfileName;
// //Proccessor Graph
juce::ToggleButton muteInputButton { "Mute Input" };
juce::Label labelSlot1 { {}, "Slot 1" };
juce::Label labelSlot2 { {}, "Slot 2" };
juce::Label labelSlot3 { {}, "Slot 3" };
juce::ComboBox processorBox1;
juce::ComboBox processorBox2;
juce::ComboBox processorBox3;
juce::ToggleButton bypassButton1 { "Bypass 1" };
juce::ToggleButton bypassButton2 { "Bypass 2" };
juce::ToggleButton bypassButton3 { "Bypass 3" };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HRTFPluginV2AudioProcessorEditor)
};
This is my editor class, I belive using the processorChoices as part of a ComboBox may be having issues, but I don’t understand why.
For context here is my full AudioProcessor class:
class HRTFPluginV2AudioProcessor : public juce::AudioProcessor,
public juce::AudioProcessorValueTreeState::Listener
#if JucePlugin_Enable_ARA
, public juce::AudioProcessorARAExtension
#endif
{
public:
//==============================================================================
HRTFPluginV2AudioProcessor();
~HRTFPluginV2AudioProcessor() override;
//==============================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
#endif
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
void processBlock (juce::AudioBuffer<double>&, juce::MidiBuffer&) override;
//==============================================================================
juce::AudioProcessorEditor* createEditor() override;
bool hasEditor() const override;
//==============================================================================
const juce::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 juce::String getProgramName (int index) override;
void changeProgramName (int index, const juce::String& newName) override;
//==============================================================================
void getStateInformation (juce::MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
//===================MY CODE================
//===============ValueTreeState===============
std::atomic<float> getAzimuth();
std::atomic<float> getElevation();
std::atomic<float> getDistance();
std::atomic<float> getAirTemperature();
std::atomic<float> getEarlyReflections();
std::atomic<float> getHumidity();
std::atomic<float> getAirPressure();
std::atomic<float> getMaxDelayTime();
std::atomic<float> getDelayTime1();
std::atomic<float> getDelayTime2();
std::atomic<float> getFeedback();
std::atomic<float> getWetLevel();
//typedef for readability:
typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
//===Setters===
void setAirTemperatureFromEditor(float newValue);
void setEarlyReflectionsFromEditor(float newValue);
void setHumidityFromEditor(float newValue);
void setAirPressureFromEditor(float newValue);
void setDistanceFromEditor(float newValue);
void setAzimuthFromEditor(float newValue);
void setElevationFromEditor(float newValue);
void setMaxDelayTimeFromEditor(float newValue);
void setDelayTime1FromEditor(float newValue);
void setDelayTime2FromEditor(float newValue);
void setFeedbackFromEditor(float newValue);
void setWetLevelFromEditor(float newValue);
void setFileName(juce::String fileName);
//===============ProcessorGraphMethods================
void updateGraph();
void initialiseGraph();
void connectMidiNodes();
void connectAudioNodes();
//===============ProcessorGraphVariables================
using AudioGraphIOProcessor = juce::AudioProcessorGraph::AudioGraphIOProcessor;
using Node = juce::AudioProcessorGraph::Node;
juce::StringArray processorChoices { "Empty", "HRTFProcessor", "AirAbsorption", "EarlyReflections" }; //list of available processors
std::unique_ptr<juce::AudioProcessorGraph> mainProcessor;
std::unique_ptr<juce::AudioParameterBool> muteInput;
std::unique_ptr<juce::AudioParameterChoice> processorSlot1;
std::unique_ptr<juce::AudioParameterChoice> processorSlot2;
std::unique_ptr<juce::AudioParameterChoice> processorSlot3;
std::unique_ptr<juce::AudioParameterBool> bypassSlot1;
std::unique_ptr<juce::AudioParameterBool> bypassSlot2;
std::unique_ptr<juce::AudioParameterBool> bypassSlot3;
//==============================================================================
//==Impulse Response ==//
juce::AudioProcessorValueTreeState treeState;
juce::ValueTree variableTree;
//===File Choosing Variables===
juce::File root, savedFile;
juce::String fileName;
//==User Choice Variables ValueTree==
juce::AudioProcessorValueTreeState userParameters;
private:
//==Slider variables to be then set in other classes==//
std::atomic<float> azimuth;
std::atomic<float> elevation;
std::atomic<float> airTemp;
std::atomic<float> humidity;
std::atomic<float> airPressure;
std::atomic<float> distance;
std::atomic<float> earlyReflectionsValue;
std::atomic<float> maxDelayTime;
std::atomic<float> delayTime1;
std::atomic<float> delayTime2;
std::atomic<float> feedback;
std::atomic<float> wetLevel;
//==atmomic Pointers to the user parameters==//
std::atomic<float>* aziParam = nullptr;
std::atomic<float>* eleParam = nullptr;
std::atomic<float>* airTempParam = nullptr;
std::atomic<float>* humidityParam = nullptr;
std::atomic<float>* airPressureParam = nullptr;
std::atomic<float>* distanceParam = nullptr;
std::atomic<float>* earlyReflectionsValueParam = nullptr;
std::atomic<float>* maxDelayTimeParam = nullptr;
std::atomic<float>* delayTime1Param = nullptr;
std::atomic<float>* delayTime2Param = nullptr;
std::atomic<float>* feedbackParam = nullptr;
std::atomic<float>* wetLevelParam = nullptr;
//===============ProcessorGraph================
Node::Ptr audioInputNode; //pointers to the main AudioProcessorGraph as well as the audio and midi input and output nodes
Node::Ptr audioOutputNode;
Node::Ptr midiInputNode;
Node::Ptr midiOutputNode;
Node::Ptr slot1Node; //pointers to the nodes for each of the 3 slots
Node::Ptr slot2Node;
Node::Ptr slot3Node;
//==============================================================================
//==Impulse Response ==//
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
void parameterChanged(const juce::String& parameterID, float newValue) override;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HRTFPluginV2AudioProcessor)
};
Any suggestions on this or other improvements would be very much appreciated
