Distorted oscillator sound


#1

I’m working on making a dub siren plugin loosely based on the AudioProcessorValueTreeState tutorial and the AudioProcessorGraph tutorial.

I currently have one oscillator in the graph with the frequency controlled by a slider. However, it sounds as if the oscillator’s output is being modulated by something - rather than a pure sine wave I get a wobbling sound. If I put the frequency right down to 1Hz rather than hearing nothing I hear a distorted oscillation with the volume going up and down at 1Hz. The output at, say, 600Hz will sound different if I move the slider to a different value and then bring it back to 600Hz.

Below is what is hopefully only the relevant bits of code (I realise the frequency range is too high for a real LFO but I don’t think that’s relevant). As you can see they are fairly similar to the tutorials so I am at a loss what to look at next. Both tutorials build and sound fine.

I thought that the problem might be making the LFO a subclass of AudioProcessorValueTreeState::Listener, but commenting out the relevant bits of code made no difference.

Possibly relevant is that I get a lot of the following error in the console:

2018-06-30 17:52:05.153012+0100 AudioPluginHost[21997:4770486] dynamic_cast error 1: Both of the following type_info's should have public visibility. At least one of them is hidden. N4juce23AudioProcessorParameterE, N4juce28AudioProcessorValueTreeState9ParameterE.

Removing the call to createAndAddParameters in the constructor removes this error but I don’t see why it should cause problems.

I would be grateful for any suggestions.

// LFO.h ================

class LFO : public ProcessorBase, public AudioProcessorValueTreeState::Listener
{
public:
    LFO();
    const String getName() const override { return "LFO"; }
    void prepareToPlay(double sampleRate, int samplesPerBlock) override;
    void processBlock(AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override;
    void reset() override;
    void parameterChanged(const String& parameterID, float newValue) override;
private:
    dsp::Oscillator<float> oscillator;
};

// LFO.cpp ==============

LFO::LFO()
{
    oscillator.setFrequency(440.0f);
    oscillator.initialise([] (float x) { return std::sin(x); });
}

void LFO::prepareToPlay(double sampleRate, int samplesPerBlock)
{
    dsp::ProcessSpec spec { sampleRate, static_cast<uint32>(samplesPerBlock) };
    oscillator.prepare(spec);
}

void LFO::processBlock(AudioSampleBuffer &buffer, juce::MidiBuffer &midiMessages)
{
    dsp::AudioBlock<float> block(buffer);
    dsp::ProcessContextReplacing<float> context(block);
    oscillator.process(context);
}

void LFO::reset()
{
    oscillator.reset();
}

void LFO::parameterChanged(const String& parameterID, float newValue)
{
    if (parameterID == "lfo_freq") {
        oscillator.setFrequency(newValue);
    }
}

// PluginProcessor.cpp ========

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

//==============================================================================
DubSirenAudioProcessor::DubSirenAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                       ),
        mainProcessor(new AudioProcessorGraph()),
        parameters(*this, nullptr)
#endif
{
    parameters.createAndAddParameter("lfo_freq", "LFO Freq", String(), NormalisableRange<float>(1.0f, 10000.0f), 440.0f, nullptr, nullptr);
    parameters.state = ValueTree(Identifier("LFOFreq"));

}

...

void DubSirenAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    if (lfoNode == nullptr) {
        mainProcessor->setPlayConfigDetails(getMainBusNumInputChannels(),
                                            getMainBusNumOutputChannels(),
                                            sampleRate, samplesPerBlock);

        mainProcessor->prepareToPlay(sampleRate, samplesPerBlock);
        initialiseGraph();
    }
}

void DubSirenAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); i++)
    {
        buffer.clear(i, 0, buffer.getNumSamples());
    }

    mainProcessor->processBlock(buffer, midiMessages);
}

void DubSirenAudioProcessor::initialiseGraph()
{
    mainProcessor->clear();

    audioOutputNode = mainProcessor->addNode(new AudioGraphIOProcessor(AudioGraphIOProcessor::audioOutputNode));
    midiInputNode = mainProcessor->addNode(new AudioGraphIOProcessor(AudioGraphIOProcessor::midiInputNode));
    midiOutputNode = mainProcessor->addNode(new AudioGraphIOProcessor(AudioGraphIOProcessor::midiOutputNode));

    connectAudioNodes();
    connectMidiNodes();
}

void DubSirenAudioProcessor::connectAudioNodes()
{
    lfoNode = mainProcessor->addNode(new LFO());

    parameters.addParameterListener("lfo_freq", static_cast<LFO*>(lfoNode->getProcessor()));

    for (int channel = 0; channel < 2; channel++)
    {
        mainProcessor->addConnection({
            { lfoNode->nodeID, channel },
            { audioOutputNode->nodeID, channel }
        });
    }
}

void DubSirenAudioProcessor::connectMidiNodes()
{
    mainProcessor->addConnection({
        { midiInputNode->nodeID, AudioProcessorGraph::midiChannelIndex},
        { midiOutputNode->nodeID, AudioProcessorGraph::midiChannelIndex}
    });
}

// PluginEditor.cpp =======

DubSirenAudioProcessorEditor::DubSirenAudioProcessorEditor (DubSirenAudioProcessor& p, AudioProcessorValueTreeState& vts)
    : AudioProcessorEditor (p), processor (p), valueTreeState(vts)
{
    setSize (400, 300);

    lfoFreqLabel.setText("LFO Freq", dontSendNotification);
    addAndMakeVisible(lfoFreqLabel);
    addAndMakeVisible(lfoFreq);
    lfoFreqAttachment.reset(new SliderAttachment(valueTreeState, "lfo_freq", lfoFreq));
}