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));
}

#2

Hey tommy, I don’t know if you fixed it already but I do the processing a bit different.

I’m no expert in graphs but if you see the AudioProcessorValueTreeState tutorial they don’t perform any processblock in the graph, but in the processors themselves: the graph just connects the processors that do the job. I see (or at least use) the graph just as an organizer, setting the input/output chains between processors, and leave all the processblock stuff to the AudioProcessor's, so just routing the output from a processor node to the input of another is enough (if you connect them to the outputnode of course, otherwise you won’t get sound!).

Tell me if that fixed it for you, cheers


#3

Hi johngalt, thanks for your reply but I’m not sure I follow.

I was following this tutorial where the TutorialProcessor implements processBlock by calling audioProcessorGraph->processBlock(). Which bit of processing do you suggest moving?

thanks

EDIT

Finally found the problem. I was missing this line in initialiseGraph:
lfoNode->getProcessor()->enableAllBuses();

I’m not sure why its absence caused the strange distortion. Interestingly, looping over every node in the graph and enabling the buses outputted silence - only enabling just the lfoNode works.


#4

Glad to se you finally found the problem. Graphs are a bit tricky until you have finally set them up and it’s easy to forget setting up some node