ValueTreeState implementation broke ADSR

After finishing my synthesiser, I wanted to add preset saving functionality. I found that I have to implement a ValueTreeState. I realized that it would’ve been easier to use this all along, but changing my code now is unavoidable. I foolishly did not save a copy of my project before this undertaking, although I do have a .vst3 of the working version (without preset saving). The problem is that after attempting to implement a ValueTreeState, I seemingly broke my ADSR implementation. It was working as expected before, but now, the attack goes from 0 to 100, instead of a gradient. The decay and sustain also do not work as expected, although the release seems to work fine. I’m not sure what could be causing this, as all I changed was how the parameters are changed, not the actual DSP, although it is possible that I unknowingly changed something. To test however, I manually set the correct parameters immediately before applying the envelope to the buffer, and I still have this problem. Below is my inherited Synthesiser class, inherited SynthesiserVoice class, plugin editor, and processor. I have spent several hours trying to find the problem to no avail. Thank you in advance.

 class SynthVoice : public SynthesiserVoice
{
public:
    SynthVoice::SynthVoice(int type)
    {
        waveType = type;
    }

    bool canPlaySound(SynthesiserSound* sound) override
    {
        return dynamic_cast<SynthesiserSound*>(sound) != nullptr;
    }

    void startNote(int midiNoteNumber, float velocity, SynthesiserSound* sound, int currentPitchWheelPosition)
    {
        if (waveType == sine)
            sinOsc.setFrequency(MidiMessage::getMidiNoteInHertz(midiNoteNumber));
        else if (waveType == triangle)
            triangleOsc.setFrequency(MidiMessage::getMidiNoteInHertz(midiNoteNumber));
        else if (waveType == saw)
            sawOsc.setFrequency(MidiMessage::getMidiNoteInHertz(midiNoteNumber));
        else if (waveType == square)
            squareOsc.setFrequency(MidiMessage::getMidiNoteInHertz(midiNoteNumber));
        adsr.noteOn();
    }

    void stopNote(float velocity, bool allowTailOff)
    {
        adsr.noteOff();
    }

    void pitchWheelMoved(int newPitchWheelValue)
    {

    }

    void controllerMoved(int controllerNumber, int newControllerValue)
    {

    }

    void prepareToPlay(double sampleRate, int samplesPerBlock, int outputChannels)
    {
        synthesisBuffer.setSize(1, samplesPerBlock, true, true, true);

        juce::dsp::ProcessSpec spec;
        spec.maximumBlockSize = samplesPerBlock;
        spec.sampleRate = sampleRate;
        spec.numChannels = outputChannels;

        sinOsc.prepare(spec);
        triangleOsc.prepare(spec);
        sawOsc.prepare(spec);
        squareOsc.prepare(spec);
    
        filter.prepare(spec);

        gain.prepare(spec);

        adsr.setSampleRate(sampleRate);
    }

    void renderNextBlock(AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
    {
        AudioBuffer<float> synthesisBufferProxy(synthesisBuffer.getArrayOfWritePointers(), 1, 0, numSamples);
        dsp::AudioBlock<float> audioBlock{ synthesisBufferProxy };

        if (waveType == sine)
            sinOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
        else if (waveType == triangle)
            triangleOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
        else if (waveType == saw)
            sawOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
        else if (waveType == square)
            squareOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));

        gain.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));

        filter.process(dsp::ProcessContextReplacing<float>(audioBlock));
    
        adsr.applyEnvelopeToBuffer(synthesisBufferProxy, 0, numSamples);

        for (int chan = 0; chan < outputBuffer.getNumChannels(); ++chan)
            outputBuffer.addFrom(chan, startSample, synthesisBufferProxy, 0, 0, numSamples, 1.0f);
    }

    enum waveTypes
    {
        sine = 1,
        triangle,
        saw,
        square
    };

    juce::ADSR::Parameters adsrParams;
    dsp::Gain<float> gain;

    int waveType = sine;

    juce::ADSR adsr;

    dsp::ProcessorDuplicator<dsp::StateVariableFilter::Filter<float>, dsp::StateVariableFilter::Parameters<float>> filter;

private:
    dsp::Oscillator<float> sinOsc{ [](float x) { return std::sin(x); } };
    dsp::Oscillator<float> triangleOsc{ [](float x) { return abs(x / MathConstants<float>::pi); } };
    dsp::Oscillator<float> sawOsc{ [](float x) { return x / MathConstants<float>::pi; } };
    dsp::Oscillator<float> squareOsc{ [](float x) { return x < 0.0f ? -1.0f : 1.0f; } };

    AudioBuffer<float> synthesisBuffer;
};

class ArbSynth : public Synthesiser
{
public:
    void ArbSynth::updateGainValue(float newGainVal)
    {
        for (auto* v : voices)
        {
            if (auto voice = dynamic_cast<SynthVoice*>(v))
            {
                voice->gain.setGainLinear(newGainVal / 50000.0f);
            }
        }
    }

    void ArbSynth::updateWaveType(int newWaveType)
    {
        for (auto* v : voices)
        {
            if (auto voice = dynamic_cast<SynthVoice*>(v))
            {
                voice->waveType = newWaveType;
            }
        }
    }

    void ArbSynth::updateADSR(float atk, float dcy, float sus, float rls)
    {
        for (auto* v : voices)
        {
            if (auto voice = dynamic_cast<SynthVoice*>(v))
            {
                voice->adsrParams.attack = 0.8f;
                voice->adsrParams.decay = 5.0f;
                voice->adsrParams.sustain = 100.0f;
                voice->adsrParams.release = 0.1f;
                voice->adsr.setParameters(voice->adsrParams);
            }
        }
    }

    void ArbSynth::updateFilter(float frequency, float resonance, dsp::StateVariableFilter::StateVariableFilterType filterType)
    {
        for (auto* v : voices)
        {
            if (auto voice = dynamic_cast<SynthVoice*>(v))
            {
                voice->filter.state->type = filterType;
                voice->filter.state->setCutOffFrequency(getSampleRate(), frequency, resonance / 10.0f);
            }
        }
    }

    void ArbSynth::prepareToPlay(double sampleRate, int samplesPerBlock, int outputChannels)
    {
        for (auto* v : voices)
        {
            if (auto voice = dynamic_cast<SynthVoice*>(v))
            {
                voice->prepareToPlay(sampleRate, samplesPerBlock, outputChannels);
            }
        }
    }
};

class ArbitraryAudioProcessorEditor  : public juce::AudioProcessorEditor, public ComboBox::Listener
{
public:
    ArbitraryAudioProcessorEditor (ArbitraryAudioProcessor&, AudioProcessorValueTreeState&);
    ~ArbitraryAudioProcessorEditor() override;

    //==============================================================================
    void paint (juce::Graphics&) override;
    void resized() override;

    void comboBoxChanged(ComboBox* box) override
    {
        if (box == &waveBox)
        {
            audioProcessor.currentWaveType = waveBox.getSelectedId();
        }
        if (box == &filterBox)
        {
            if (filterBox.getSelectedId() == 1)
                audioProcessor.filterOn = false;
            else if (filterBox.getSelectedId() == 2)
            {
                audioProcessor.currentFilterType = dsp::StateVariableFilter::StateVariableFilterType::lowPass;
                audioProcessor.filterOn = true;
            }
            else if (filterBox.getSelectedId() == 3)
            {
                audioProcessor.currentFilterType = dsp::StateVariableFilter::StateVariableFilterType::highPass;
                audioProcessor.filterOn = true;
            }
            else if (filterBox.getSelectedId() == 4)
            {
                audioProcessor.currentFilterType = dsp::StateVariableFilter::StateVariableFilterType::bandPass;
                audioProcessor.filterOn = true;
            }
        }
    }

    juce::AudioProcessorValueTreeState& valueTreeState;

    typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment;
    typedef juce::AudioProcessorValueTreeState::ComboBoxAttachment ComboBoxAttachment;

private:
    // This reference is provided as a quick way for your editor to
    // access the processor object that created it.
    ArbitraryAudioProcessor& audioProcessor;

    Label masterLabel;

    Label oscLabel;

    Label filterLabel;

    Label envLabel;

    Slider gainSlider;
    Label gainLabel;
    std::unique_ptr<SliderAttachment> gainAttachment;

    Slider freqSlider;
    Label freqLabel;
    std::unique_ptr<SliderAttachment> freqAttachment;

    Slider resSlider;
    Label resLabel;
    std::unique_ptr<SliderAttachment> resAttachment;

    Slider atkSlider;
    Label atkLabel;
    std::unique_ptr<SliderAttachment> atkAttachment;

    Slider dcySlider;
    Label dcyLabel;
    std::unique_ptr<SliderAttachment> dcyAttachment;

    Slider susSlider;
    Label susLabel;
    std::unique_ptr<SliderAttachment> susAttachment;

    Slider rlsSlider;
    Label rlsLabel;
    std::unique_ptr<SliderAttachment> rlsAttachment;

    ComboBox waveBox;
    Label waveLabel;

    ComboBox filterBox;
    Label filterTypeLabel;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ArbitraryAudioProcessorEditor)
};

ArbitraryAudioProcessorEditor::ArbitraryAudioProcessorEditor (ArbitraryAudioProcessor& p, AudioProcessorValueTreeState& vts)
    : AudioProcessorEditor (&p), audioProcessor (p),
    valueTreeState(vts)
{
    // Make sure that before the constructor has finished, you've set the
    // editor's size to whatever you need it to be.
    setSize (800, 300);

    addAndMakeVisible(&masterLabel);
    masterLabel.setText("Master", dontSendNotification);

    addAndMakeVisible(&oscLabel);
    oscLabel.setText("Oscillator", dontSendNotification);

    addAndMakeVisible(&filterLabel);
    filterLabel.setText("Filter", dontSendNotification);

    addAndMakeVisible(&envLabel);
    envLabel.setText("Envelope", dontSendNotification);

    addAndMakeVisible(&gainSlider);
    gainSlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    gainSlider.setTextBoxStyle(Slider::TextBoxRight, true, 40, 20);
    gainSlider.setTextValueSuffix("%");
    gainAttachment.reset(new SliderAttachment(valueTreeState, "gain", gainSlider));
    gainSlider.setRange(0.0f, 99.0f, 1.0f);
    gainSlider.setValue(50.0f);

    addAndMakeVisible(&gainLabel);
    gainLabel.setText("Gain", dontSendNotification);

    addAndMakeVisible(&atkSlider);
    atkSlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    atkSlider.setTextBoxStyle(Slider::TextBoxRight, true, 50, 20);
    atkSlider.setTextValueSuffix("s");
    atkAttachment.reset(new SliderAttachment(valueTreeState, "atk", atkSlider));
    atkSlider.setRange(0.0f, 5.0f, 0.02f);
    atkSlider.setValue(0.05f);

    addAndMakeVisible(&atkLabel);
    atkLabel.setText("Attack", dontSendNotification);

    addAndMakeVisible(&dcySlider);
    dcySlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    dcySlider.setTextBoxStyle(Slider::TextBoxRight, true, 50, 20);
    dcySlider.setTextValueSuffix("s");
    dcyAttachment.reset(new SliderAttachment(valueTreeState, "dcy", dcySlider));
    dcySlider.setRange(0.0f, 5.0f, 0.02f);
    dcySlider.setValue(5.0f);

    addAndMakeVisible(&dcyLabel);
    dcyLabel.setText("Decay", dontSendNotification);

    addAndMakeVisible(&susSlider);
    susSlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    susSlider.setTextBoxStyle(Slider::TextBoxRight, true, 45, 20);
    susSlider.setTextValueSuffix("%");
    susAttachment.reset(new SliderAttachment(valueTreeState, "sus", susSlider));
    susSlider.setRange(0.0f, 100.0f, 1.0f);
    susSlider.setValue(100.0f);

    addAndMakeVisible(&susLabel);
    susLabel.setText("Sustain", dontSendNotification);

    addAndMakeVisible(&rlsSlider);
    rlsSlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    rlsSlider.setTextBoxStyle(Slider::TextBoxRight, true, 50, 20);
    rlsSlider.setTextValueSuffix("s");
    rlsAttachment.reset(new SliderAttachment(valueTreeState, "rls", rlsSlider));
    rlsSlider.setRange(0.0f, 5.0f, 0.02f);
    rlsSlider.setValue(0.05f);

    addAndMakeVisible(&rlsLabel);
    rlsLabel.setText("Release", dontSendNotification);

    addAndMakeVisible(&freqSlider);
    freqSlider.setSkewFactor(0.34);
    freqSlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    freqSlider.setTextBoxStyle(Slider::TextBoxRight, true, 60, 20);
    freqSlider.setTextValueSuffix("Hz");
    freqAttachment.reset(new SliderAttachment(valueTreeState, "freq", freqSlider));
    freqSlider.setRange(40.0f, 20000.0f, 10.0f);
    freqSlider.setValue(400.0f);

    addAndMakeVisible(&freqLabel);
    freqLabel.setText("Cutoff Frequency", dontSendNotification);

    addAndMakeVisible(&resSlider);
    resSlider.setSliderStyle(Slider::SliderStyle::LinearVertical);
    resSlider.setTextBoxStyle(Slider::TextBoxRight, true, 40, 20);
    resAttachment.reset(new SliderAttachment(valueTreeState, "res", resSlider));
    resSlider.setRange(1.0f, 100.0f, 1.0f);
    resSlider.setValue(5.0f);

    addAndMakeVisible(&resLabel);
    resLabel.setText("Resonance", dontSendNotification);

    addAndMakeVisible(&waveBox);
    waveBox.addItem("Sine", 1);
    waveBox.addItem("Triangle", 2);
    waveBox.addItem("Saw", 3);
    waveBox.addItem("Square", 4);
    waveBox.setSelectedId(1);
    waveBox.addListener(this);

    addAndMakeVisible(&waveLabel);
    waveLabel.setText("Waveform", dontSendNotification);

    addAndMakeVisible(&filterBox);
    filterBox.addItem("None", 1);
    filterBox.addItem("LP", 2);
    filterBox.addItem("HP", 3);
    filterBox.addItem("BP", 4);
    filterBox.setSelectedId(1);
    filterBox.addListener(this);

    addAndMakeVisible(&filterTypeLabel);
    filterTypeLabel.setText("Type", dontSendNotification);
}

ArbitraryAudioProcessorEditor::~ArbitraryAudioProcessorEditor()
{
}

//==============================================================================
void ArbitraryAudioProcessorEditor::paint (juce::Graphics& g)
{
    // (Our component is opaque, so we must completely fill the background with a solid colour)
    g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));

    g.setColour(Colours::white);

    g.fillRect(0, 20, 51, 1);
    g.fillRect(50, 0, 1, 20);

    g.fillRect(107, 0, 1, 300);
    g.fillRect(107, 20, 60, 1);
    g.fillRect(166, 0, 1, 20);

    g.fillRect(219, 0, 1, 300);
    g.fillRect(219, 17, 40, 1);
    g.fillRect(258, 0, 1, 18);

    g.fillRect(405, 0, 1, 300);
    g.fillRect(405, 17, 60, 1);
    g.fillRect(465, 0, 1, 18);
}

void ArbitraryAudioProcessorEditor::resized()
{
    // This is generally where you'll want to lay out the positions of any
    // subcomponents in your editor..
    masterLabel.setBounds(0, 5, 50, 10);

    oscLabel.setBounds(105, 5, 65, 10);

    filterLabel.setBounds(220, 3, 40, 10);

    envLabel.setBounds(406, 1, 60, 15);

    gainSlider.setBounds(32, 19, 70, 62);
    gainLabel.setBounds(0, 46, 40, 10);

    freqSlider.setBounds(223, 74, 100, 200);
    freqLabel.setBounds(248, 140, 120, 15);

    resSlider.setBounds(327, 228, 65, 62);
    resLabel.setBounds(258, 254, 75, 12);

    atkSlider.setBounds(414, 24, 85, 200);
    atkLabel.setBounds(448, 96, 48, 12);

    dcySlider.setBounds(494, 24, 85, 200);
    dcyLabel.setBounds(531, 95, 46, 14);

    susSlider.setBounds(574, 24, 80, 200);
    susLabel.setBounds(604, 96, 55, 12);

    rlsSlider.setBounds(654, 24, 85, 200);
    rlsLabel.setBounds(686, 96, 55, 12);

    waveBox.setBounds(111, 45, 100, 20);
    waveLabel.setBounds(124, 28, 73, 10);

    filterBox.setBounds(223, 45, 80, 20);
    filterTypeLabel.setBounds(238, 25, 40, 15);
}

class ArbitraryAudioProcessor  : public juce::AudioProcessor
{
public:
    //==============================================================================
    ArbitraryAudioProcessor();
    ~ArbitraryAudioProcessor() 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;

    //==============================================================================
    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;

    void updateParameters();

    ArbSynth synth;

    std::atomic<float>* gainParameter = nullptr;

    std::atomic<float>* freqParameter = nullptr;
    std::atomic<float>* resParameter = nullptr;

    std::atomic<float>* atkParameter = nullptr;
    std::atomic<float>* dcyParameter = nullptr;
    std::atomic<float>* susParameter = nullptr;
    std::atomic<float>* rlsParameter = nullptr;

    dsp::StateVariableFilter::StateVariableFilterType currentFilterType = dsp::StateVariableFilter::StateVariableFilterType::lowPass;
    bool filterOn = false;

    int currentWaveType = 0;

    AudioProcessorValueTreeState parameters;

private:
    //==============================================================================

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ArbitraryAudioProcessor)
};

ArbitraryAudioProcessor::ArbitraryAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  juce::AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
                     #endif
                       ),
    parameters(*this, nullptr, juce::Identifier("ArbIdentifier"),
        {
            std::make_unique<juce::AudioParameterFloat>("gain", "Gain", 0, 100, 50),

            std::make_unique<juce::AudioParameterFloat>("freq", "Cutoff Frequency", 40, 20000, 400),
            std::make_unique<juce::AudioParameterFloat>("res", "Resonance", 0, 100, 1),

            std::make_unique<juce::AudioParameterFloat>("atk", "Attack", 0.0f, 5.0f, 0.05f),
            std::make_unique<juce::AudioParameterFloat>("dcy", "Decay", 0.0f, 5.0f, 5.0f),
            std::make_unique<juce::AudioParameterFloat>("sus", "Sustain", 0, 100, 100),
            std::make_unique<juce::AudioParameterFloat>("rls", "Release", 0.0f, 5.0f, 0.05f)
        })
#endif
{
    gainParameter = parameters.getRawParameterValue("gain");

    freqParameter = parameters.getRawParameterValue("freq");
    resParameter = parameters.getRawParameterValue("res");

    atkParameter = parameters.getRawParameterValue("atk");
    dcyParameter = parameters.getRawParameterValue("dcy");
    susParameter = parameters.getRawParameterValue("sus");
    rlsParameter = parameters.getRawParameterValue("rls");

    synth.addSound(new SynthSound());

    synth.clearVoices();
    for (int i = 0; i < 1; i++)
        synth.addVoice(new SynthVoice(currentWaveType));
}

ArbitraryAudioProcessor::~ArbitraryAudioProcessor()
{
}

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

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

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

bool ArbitraryAudioProcessor::isMidiEffect() const
{
   #if JucePlugin_IsMidiEffect
    return true;
   #else
    return false;
   #endif
}

double ArbitraryAudioProcessor::getTailLengthSeconds() const
{
    return 0.0;
}

int ArbitraryAudioProcessor::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 ArbitraryAudioProcessor::getCurrentProgram()
{
    return 0;
}

void ArbitraryAudioProcessor::setCurrentProgram (int index)
{
}

const juce::String ArbitraryAudioProcessor::getProgramName (int index)
{
    return {};
}

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

//==============================================================================
void ArbitraryAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    synth.setCurrentPlaybackSampleRate(sampleRate);
    synth.prepareToPlay(sampleRate, samplesPerBlock, getTotalNumOutputChannels());
}

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

#ifndef JucePlugin_PreferredChannelConfigurations
bool ArbitraryAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
  #if JucePlugin_IsMidiEffect
    juce::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() != juce::AudioChannelSet::mono()
     && layouts.getMainOutputChannelSet() != juce::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 ArbitraryAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    synth.updateGainValue(*gainParameter);
    synth.updateWaveType(currentWaveType);
    //synth.updateADSR(*atkParameter, *dcyParameter, *susParameter, *rlsParameter);

    if (filterOn)
        synth.updateFilter(*freqParameter, *resParameter, currentFilterType);
    else
        synth.updateFilter(20000.0f, 0.1f, dsp::StateVariableFilter::StateVariableFilterType::lowPass);

    synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());
}

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

juce::AudioProcessorEditor* ArbitraryAudioProcessor::createEditor()
{
    return new ArbitraryAudioProcessorEditor (*this, parameters);
}

//==============================================================================
void ArbitraryAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    auto state = parameters.copyState();
    std::unique_ptr<juce::XmlElement> xml(state.createXml());
    copyXmlToBinary(*xml, destData);
}

void ArbitraryAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    std::unique_ptr<juce::XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));

    if (xmlState.get() != nullptr)
        if (xmlState->hasTagName(parameters.state.getType()))
            parameters.replaceState(juce::ValueTree::fromXml(*xmlState));
}

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

I’m sorry to hear this, and don’t have any comment about your actual problem, but I just wanted to say that you really should use source control (eg. git) even if you are a single developer.

You will get a backup like this if you push to github/gitlab etc., and you can try new features in a separate branch before merging to your main branch, which would have allowed you to easily back out of the changes you were making.

It’s really worthwhile, and if you’ve never used it before will be a useful skill to learn, since it is guaranteed to be used in any professional setting.

4 Likes

I have found some code from the earlier working version, the renderNextBlock method is completely unchanged unless I’m looking at it wrong.

Current code:

void renderNextBlock(AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
{
    AudioBuffer<float> synthesisBufferProxy(synthesisBuffer.getArrayOfWritePointers(), 1, 0, numSamples);
    dsp::AudioBlock<float> audioBlock{ synthesisBufferProxy };

    if (waveType == sine)
        sinOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
    else if (waveType == triangle)
        triangleOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
    else if (waveType == saw)
        sawOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
    else if (waveType == square)
        squareOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));

    gain.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));

    filter.process(dsp::ProcessContextReplacing<float>(audioBlock));
    
    adsr.applyEnvelopeToBuffer(synthesisBufferProxy, 0, numSamples);

    for (int chan = 0; chan < outputBuffer.getNumChannels(); ++chan)
        outputBuffer.addFrom(chan, startSample, synthesisBufferProxy, 0, 0, numSamples, 1.0f);
}

Old code:

void renderNextBlock(AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
{
    AudioBuffer<float> synthesisBufferProxy (synthesisBuffer.getArrayOfWritePointers(), 1, 0, numSamples);
dsp::AudioBlock<float> audioBlock{ synthesisBufferProxy };

    if (waveType == sine)
        sinOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
    else if (waveType == triangle)
        triangleOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
    else if (waveType == saw)
        sawOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));
    else if (waveType == square)
        squareOsc.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));

    gain.process(juce::dsp::ProcessContextReplacing<float>(audioBlock));

    adsr.applyEnvelopeToBuffer(synthesisBufferProxy, 0, numSamples);

    for (int chan = 0; chan < outputBuffer.getNumChannels(); ++chan)
        outputBuffer.addFrom(chan, startSample, synthesisBufferProxy, 0, 0, numSamples, 1.0f);
}

EDIT:
I have analyzed the sound my synthesiser makes using audacity; it seems to play very quietly and slightly ramp up during the attack stage, and then suddenly jump up.

I figured out the problem. The sustain parameter of the ADSR should be between 0 and 1, but when I changed my code in the plugin editor, I made it between 0 and 100, and having it set to 100 produces the effect shown above.