How to Dynamically Change an Oscillator's Waveform

Hi there! I’m trying to create a plugin that allows a user to change various settings of the synthesiser during runtime & trying to figure out a) if that’s always possible, & b) if so, how to implement it. My first thing I’m trying to add is a ComboBox that allows a user to change the waveform after the project is initialized. I have the logic of choosing the waveform on the UI & the constructor has an if/else branch to choose which waveform to initialize, but I’m running into an issue figuring out how to actually trigger that change. I’ve copied some code snippets below, & will give an overview of how the project is set up. Would appreciate any help or guidance in this topic!

The project is loosely based on the intro to DSP tutorial, just changed to a plugin format & broken out into class files. PluginProcessor.cpp calls AudioEngine.cpp, which in turn calls Voice.cpp, which calls Oscillator.cpp.

This code snippet gets the user input from the ComboBox, & works as I want, storing the value in the float select. I couldn’t tell whether I wanted to implement chooseWave() in Oscillator or Voice, so I tried both.

void FullPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    buffer.clear();
    
    float select = apvts.getRawParameterValue("Waveform Selection")->load();
    voice.chooseWave (select);
    //osc.chooseWave (select);
    ....
}

Here’s my chooseWave() in Voice. (Note that this is the same method I implemented in Oscillator)

void Voice::chooseWave(float select)
{
    processorChain.reset();
}

...

//private member of Voice.h
juce::dsp::ProcessorChain<Oscillator<float>, Oscillator<float>, juce::dsp::LadderFilter<float>,
    juce::dsp::Gain<float>> processorChain;

Here’s my Oscillator constructor, which is called from the Voice class’s private member processorChain, shown above. I realize I’m never sending in the select value to the constructor, but not sure how I can do that.

template <typename Type>
class Oscillator
{
public:
    Oscillator(float select = 0)
    {        
        auto& osc = processorChain.template get<oscIndex>();
        if (select == 1) // Square Wave
        {
            osc.initialise([](Type x)
                           {
                               return (x < 0) ? -1 : 1;
                           }, 128);
        } else if (select == 2) // Sawtooth Wave
        {
            osc.initialise([](Type x)
                           {
                               return juce::jmap (x,
                                                  Type (-juce::MathConstants<double>::pi),
                                                  Type (juce::MathConstants<double>::pi),
                                                  Type (-1),
                                                  Type (1));
                           }, 2);
        }
        else // Sine Wave
        {
            osc.initialise([](Type x)
                           {
                               return std::sin(x);
                           }, 128);
        }
    }

To answer your questions:

a) Yes that’s possible! That’s the most fun part of synthesizers, changing them during runtime.

b) I’m not sure what you’re processorChain.reset() function is doing. But you could implement a function in your oscillator class that lets you set the waveform. Then when processing the oscillator, check what waveform it should produce and calculate that waveform.

Make sure you are changing the waveform in a thread-safe way. Using the atomic floats with apvts.getRawParameterValue() is good practice.

Ahhh I got it! Realized I was creating another Oscillator instance & changing that waveform rather than the one from processorChain. Thanks for the help!