VST3 making two oscillators?

#1

Hello guys, I am still working on the VST3, and have reached a stage where I have an oscillator, but want to have two oscillators that can manipulate one sound. So far I have made one oscillator class, and two GUI editor of the oscillator class which are basically identical one is for the 1st oscillator, while the 2nd is for the 2nd oscillator. I basically did the same thing in the process block for the second oscillator that I had did to the first.

OscillatorEditor.h:

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

class OscillatorEditor    : public Component,
                            private ComboBox::Listener
{
public:
    OscillatorEditor(SharodVstAudioProcessor&);
    ~OscillatorEditor();

    void paint (Graphics&) override;
    void resized() override;
    void comboBoxChanged(ComboBox*) override;
private:
    SharodVstAudioProcessor& processor;
    ComboBox oscMenu;

    std::unique_ptr<AudioProcessorValueTreeState::ComboBoxAttachment> waveSelection;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscillatorEditor)
};

OscillatorEditor.cpp:

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

OscillatorEditor::OscillatorEditor(SharodVstAudioProcessor& p) : processor(p)
{
    setSize(300, 300);

    oscMenu.addItem("Sine", 1);
    oscMenu.addItem("Saw", 2);
     oscMenu.addItem("Square", 3);
    oscMenu.setJustificationType(Justification::centred);
    addAndMakeVisible(&oscMenu);
    oscMenu.addListener(this);
    
    waveSelection = std::make_unique<AudioProcessorValueTreeState::ComboBoxAttachment>(processor.tree, "waveType", oscMenu);
}

OscillatorEditor::~OscillatorEditor()
{
}

void OscillatorEditor::paint (Graphics& g)
{
    Rectangle<int> titleArea (0, 10, getWidth(), 20);
    
    g.setColour(Colours::white);
    g.drawText("Oscillator", titleArea, Justification::centredTop);
    
  

}

void OscillatorEditor::resized()
{
    Rectangle<int> area = getLocalBounds().reduced(40);
    oscMenu.setBounds(area.removeFromTop(20));

}
void OscillatorEditor::comboBoxChanged(ComboBox* box)
{
    
}

OscillatorEditor2.h:

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

class OscillatorEditor2    : public Component,
private ComboBox::Listener
{
public:
    OscillatorEditor2(SharodVstAudioProcessor&);
    ~OscillatorEditor2();
    
    void paint (Graphics&) override;
    void resized() override;
    void comboBoxChanged(ComboBox*) override;
private:
    SharodVstAudioProcessor& processor;
    ComboBox oscMenu2;
    
    std::unique_ptr<AudioProcessorValueTreeState::ComboBoxAttachment> waveSelection;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscillatorEditor2)
};

OscillatorEditor2.cpp:

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

OscillatorEditor2::OscillatorEditor2(SharodVstAudioProcessor& p) : processor(p)
{
    
    setSize(300, 300);
    
    oscMenu2.addItem("Sine", 1);
    oscMenu2.addItem("Saw", 2);
    oscMenu2.addItem("Square", 3);
    oscMenu2.setJustificationType(Justification::centred);
    addAndMakeVisible(&oscMenu2);
    oscMenu2.addListener(this);
    
    
    waveSelection = std::make_unique<AudioProcessorValueTreeState::ComboBoxAttachment>(processor.tree, "waveType2", oscMenu2);
    
}

OscillatorEditor2::~OscillatorEditor2()
{
}

void OscillatorEditor2::paint (Graphics& g)
{
    Rectangle<int> titleArea (0, 10, getWidth(), 20);
    
    g.setColour(Colours::white);
    g.drawText("Oscillator 2", titleArea, Justification::centredTop);
    
   // Rectangle <float> area (25, 75, 150, 150);
    
   // g.setColour(Colours::grey);
   // g.drawRoundedRectangle(area, 10.0f, 1.0f);
   
}

void OscillatorEditor2::resized()
{
    Rectangle<int> area = getLocalBounds().reduced(40);
    oscMenu2.setBounds(area.removeFromTop(20));
}
void OscillatorEditor2::comboBoxChanged(ComboBox* box)
{
    
}

SynthesizerVoice.h:

class SynthesizerVoice:public SynthesiserVoice
{
public:
    
    bool canPlaySound(SynthesiserSound* sound){
        return dynamic_cast<SynthesizerSound*>(sound) != nullptr;
    }
   
    double setWaveType ()
    {
        if (wave == 0)
        {
            return osc1.sinewave(frequency);
        }
        
        if (wave == 1)
        {
            return osc1.saw(frequency);
        }
        
        if (wave == 2)
        {
            return osc1.square(frequency);
        }
        else
        {
            return osc1.sinewave(frequency);
        }
    }
    double setWaveType2 ()
    {
        if (wave == 0)
        {
            return osc2.sinewave(frequency);
        }
        
        if (wave == 1)
        {
            return osc2.saw(frequency);
        }
        
        if (wave == 2)
        {
            return osc2.square(frequency);
        }
        else
        {
            return osc2.sinewave(frequency);
        }
    }
 
    void getFilterParams(float* filterType, float* filterCutOff, float* filterRes){
        filterKind = *filterType;
        cutOff = *filterCutOff;
        resonance = *filterRes;
    }
    //===================

    void getWaveType(float* selection)
    {
        wave = *selection;
    }
    double setEnvelope(){
        return (env1.adsr(setWaveType(), env1.trigger)*level);
    }
    double setEnvelope2(){
        return (env1.adsr(setWaveType2(), env1.trigger)*level);
    }
    void getEnvelopeParams(float* attack, float* decay, float* sustain, float* release)
    {
        env1.setAttack(*attack);
        env1.setDecay(*decay);
        env1.setSustain(*sustain);
        env1.setRelease(*release);
    }
    void  startNote(int midiNoteNumber, float velocity, SynthesiserSound* sound, int currentPitchWheelPosition){
        env1.trigger = 1;
        frequency = MidiMessage::getMidiNoteInHertz(midiNoteNumber);
        level = 50;
    }
    void stopNote(float velocity, bool allowTailOff){
        env1.trigger = 0;
        allowTailOff = true;
        
        if(velocity == 0){
            clearCurrentNote();
        }
    }
    void pitchWheelMoved(int newPitchWheelValue){
        
    }
    void controllerMoved(int controllerNumber, int newControllerValue){
        
    }
    void renderNextBlock(AudioBuffer<float> &outputBuffer, int startSample, int numSamples){
        for(int sample = 0; sample < numSamples; ++sample){

            for(int channel = 0; channel < outputBuffer.getNumChannels(); ++channel){
                outputBuffer.addSample(channel, startSample, setEnvelope()*0.3f);
               // outputBuffer.addSample(channel, startSample, setEnvelope2()*0.3f);

            }
            ++startSample;
        }
        
    }
    
    void getParameter(float* attack, float* decay, float* sustain, float* release){
        env1.setAttack(double(*attack));
        env1.setDecay(double(*decay));
        env1.setSustain(double(*sustain));
        env1.setRelease(double(*release));
    }
private:
   double level;
   double frequency;
    int wave;
    int filterKind;
    float cutOff;
    float resonance;
    
    Osc osc1;
    Osc osc2;
    oscEnv env1;
};

PluginProcessor.h:

#include "../JuceLibraryCode/JuceHeader.h"
#include "SynthesizerSound.h"
#include "SynthesizerVoice.h"
//==============================================================================
/**
*/
class SharodVstAudioProcessor  : public AudioProcessor
{
public:
    float noteOnVel;
    //==============================================================================
    SharodVstAudioProcessor();
    ~SharodVstAudioProcessor();
    
    
    
    //==============================================================================
    void prepareToPlay (double sampleRate, int samplesPerBlock) override;
    void releaseResources() override;
    void getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill);
   #ifndef JucePlugin_PreferredChannelConfigurations
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
   #endif

    void updateFilter();
    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;
    
    
    float attackTime;
    float decayTime;
    float sustainTime;
    float releaseTime;
    
    AudioProcessorValueTreeState tree;
    
private:
    Synthesiser magicSynth;
    SynthesizerVoice* magicVoice;
    Synthesiser magicSynth2;
    SynthesizerVoice* magicVoice2;
    
      dsp::ProcessorDuplicator<dsp::StateVariableFilter::Filter<float> ,
      dsp::StateVariableFilter::Parameters<float>> stateVariableFilter;

    double volume;
    double lastSampleRate;
    double currentSampleRate = 0.0;
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharodVstAudioProcessor)
};

PluginProcessor.cpp:

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

SharodVstAudioProcessor::SharodVstAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       ),
attackTime(0.1f),
tree(*this, nullptr, "Parameters", {std::make_unique<AudioParameterFloat>("attack", "Attack", 0.1f, 5000.0f, 0.1f), std::make_unique<AudioParameterFloat>("decay", "Decay", 0.1f, 500.0f, 0.1f), std::make_unique<AudioParameterFloat>("sustain", "Sustain", 0.1f, 5000.0f, 0.1f), std::make_unique<AudioParameterFloat>("release", "Release", 0.1f, 5000.0f, 0.1f),
    std::make_unique<AudioParameterFloat>("waveType", "WaveType", 0, 2, 0),
    std::make_unique<AudioParameterFloat>("waveType2", "WaveType2", 0, 2, 0),
    std::make_unique<AudioParameterFloat>("filterCutOff", "FilterCutOff", 20.0f, 10000.0f, 400.0f),
    std::make_unique<AudioParameterFloat>("filterRes", "filterRes", 1, 5, 1),
    std::make_unique<AudioParameterFloat>("filterType", "FilterType", 0, 2, 0),
    std::make_unique<AudioParameterFloat>("gain", "Gain", 0.0f, 1.0f, 0.5f)})
#endif
{

    magicSynth.clearVoices();
    
    for(int i = 0; i<5; i++)
    {
        magicSynth.addVoice (new SynthesizerVoice());
    }
    magicSynth.clearSounds();
    magicSynth.addSound(new SynthesizerSound());
    
    magicSynth2.clearVoices();
    
    for(int i = 0; i<5; i++)
    {
        magicSynth2.addVoice (new SynthesizerVoice());
    }
    magicSynth2.clearSounds();
    magicSynth2.addSound(new SynthesizerSound());
}
SharodVstAudioProcessor::~SharodVstAudioProcessor()
{
}

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

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

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

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

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

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

void SharodVstAudioProcessor::setCurrentProgram (int index)
{
}

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

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

//==============================================================================
void SharodVstAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
    
    ignoreUnused(samplesPerBlock);
    lastSampleRate = sampleRate;
    magicSynth.setCurrentPlaybackSampleRate(lastSampleRate);
    magicSynth2.setCurrentPlaybackSampleRate(lastSampleRate);

    dsp::ProcessSpec spec;
    spec.sampleRate = lastSampleRate;
    spec.maximumBlockSize = samplesPerBlock;
    spec.numChannels = getTotalNumOutputChannels();
    
    stateVariableFilter.reset();
    stateVariableFilter.prepare(spec);
    updateFilter();
}
//==============================================================================
void SharodVstAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
}
//==============================================================================
void SharodVstAudioProcessor::getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill){
    bufferToFill.clearActiveBufferRegion();
    MidiBuffer incomingMidi;
    magicSynth.renderNextBlock (*bufferToFill.buffer, incomingMidi,
                           bufferToFill.startSample, bufferToFill.numSamples);
    
    magicSynth2.renderNextBlock (*bufferToFill.buffer, incomingMidi,
                                bufferToFill.startSample, bufferToFill.numSamples);
}
#ifndef JucePlugin_PreferredChannelConfigurations
//==============================================================================
bool SharodVstAudioProcessor::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 SharodVstAudioProcessor::updateFilter()
{
    int filterChoice = *tree.getRawParameterValue("filterType");
    int freq = *tree.getRawParameterValue("filterCutOff");
    int res = *tree.getRawParameterValue("filterRes");
    
    if (filterChoice == 0)
    {
        stateVariableFilter.state->type = dsp::StateVariableFilter::Parameters<float>::Type::lowPass;
        stateVariableFilter.state->setCutOffFrequency(lastSampleRate, freq, res);
    }
    
    if (filterChoice == 1)
    {
        stateVariableFilter.state->type = dsp::StateVariableFilter::Parameters<float>::Type::highPass;
        stateVariableFilter.state->setCutOffFrequency(lastSampleRate, freq, res);
    }
    
    if (filterChoice == 2)
    {
        stateVariableFilter.state->type = dsp::StateVariableFilter::Parameters<float>::Type::bandPass;
        stateVariableFilter.state->setCutOffFrequency(lastSampleRate, freq, res);
    }
}
//==============================================================================
void SharodVstAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    
    volume = 0.5;
    for(int i = 0; i < magicSynth.getNumVoices(); i++){
        if(magicVoice = dynamic_cast<SynthesizerVoice*>(magicSynth.getVoice(i))){
            
            magicVoice->getParameter(tree.getRawParameterValue("attack"), tree.getRawParameterValue("decay"), tree.getRawParameterValue("sustain"), tree.getRawParameterValue("release"));
            
            magicVoice->getWaveType(tree.getRawParameterValue("waveType"));
            magicVoice->getWaveType(tree.getRawParameterValue("waveType2"));

            magicVoice->getFilterParams(tree.getRawParameterValue("filterType"),
                            tree.getRawParameterValue("filterCutOff"),
                                        tree.getRawParameterValue("filterRes"));
        }
    }
    
    
    buffer.clear();
    magicSynth.renderNextBlock(buffer, midiMessages,0, buffer.getNumSamples());
    updateFilter();
    dsp::AudioBlock<float> block (buffer);
    stateVariableFilter.process(dsp::ProcessContextReplacing<float> (block));
}

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

AudioProcessorEditor* SharodVstAudioProcessor::createEditor()
{
    return new SharodVstAudioProcessorEditor (*this);
}

//==============================================================================
void SharodVstAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    //MemoryOutputStream(destData, true).writeFloat(*gain);
}

void SharodVstAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    //gain->setValueNotifyingHost (MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).readFloat());
    
}

//==============================================================================

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

In this method

void SharodVstAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)

Im not sure how to go about this processing because I want one envelope object to manipulate both the oscillator sounds simultaneously.

I tried to make this as clear as I could, sorry in advance.
Thank You for the help!

0 Likes

#2

I actually ended up making the two oscillators, and they both manipulate one sound. :smiley: sorry for the waste of time.

0 Likes