Intro to DSP Observations/Questions

Hi, so I’ve just finished up the Intro to DSP section and I’ve noticed a couple of things happen when attempting to expand on what I’ve learned.

  1. Adding a 3rd OSC to the Voice class doesn’t create more sound, and as a bonus, the scope no longer outputs any information.
  2. Trying to use the LFO to change the resonance at a slower speed causes the sound to instantly spike, once a note is played and then immediately goes silent until I close the app.
if (lfoUpdateCounter == 0)
{
    lfoUpdateCounter = lfoUpdateRate;
    auto lfoOut = lfo.processSample(0.0f); 
    auto curoffFreqHz = juce::jmap(lfoOut, -1.0f, 1.0f, 100.0f, 2000.0f); 
    processorChain.get<filterIndex>().setCutoffFrequencyHz(curoffFreqHz);
    //   ********  Update Res Here  *****************
    processorChain.get<filterIndex>().setResonance(curoffFreqHz * .2); 
}

If anyone has any tips for me, they’d be appreciated :slight_smile:

The concepts here were/aren’t very easy for me to grasp(mostly from a programming standpoint) and I’m not sure how to go about understanding them more. I’m hoping that moving to the next tutorial will enlighten.

Without seeing your full code, very difficult to say why adding the new oscillator doesn’t work. Maybe you forgot to change all the relevant parts in the code that deal with the oscillators? Since the oscillators are in a ProcessorChain, things can’t be automatically iterated (at least easily).

For example, in this method :

void noteStarted() override
{
    auto velocity = getCurrentlyPlayingNote().noteOnVelocity.asUnsignedFloat();
    auto freqHz = (float) getCurrentlyPlayingNote().getFrequencyInHertz();

    processorChain.get<osc1Index>().setFrequency (freqHz, true);
    processorChain.get<osc1Index>().setLevel (velocity);

    processorChain.get<osc2Index>().setFrequency (freqHz * 1.01f, true);    // [3]
    processorChain.get<osc2Index>().setLevel (velocity);                    // [4]
}

You would need to manually add handling for the 3rd oscillator. Maybe something like :

    processorChain.get<osc3Index>().setFrequency (freqHz * 0.5f, true);    
    processorChain.get<osc3Index>().setLevel (velocity);                    

Regarding the filter resonance, you may be setting the resonance to some value that isn’t allowed. You are not using the LFO in your code at a different speed but rather reusing the same value you already used for the filter frequency, scaled by 0.2. You would need a second LFO object if you want to modulate the resonance at a different speed. edit : yeah, checking in the Juce code, the resonance value should be equal or greater than 0 and less than 1, but your code will make it go past those limits.

1 Like

Hi,
Thanks for the reply!

yeah, mine looked almost exactly like that, except my multiplier was -1 at one point, then -12 at another during my experimentation with it.

but under jmap, sourceRangeMin is set to -1.0 and sourceRangeMax to 1.0. targetRangeMin/Max to 100/2000. Not sure what it all means exactly, but that’s what I see.
Where in the code did you find those limits?

Here’s a full copy of the Voice class, if you’re interested (with updates, based on your suggestions):

class Voice  : public juce::MPESynthesiserVoice
{
public:
    Voice()
    {
        auto& masterGain = processorChain.get<masterGainIndex>();
        masterGain.setGainLinear (0.7f);

        auto& filter = processorChain.get<filterIndex>();
        filter.setCutoffFrequencyHz(1000.0f);
        filter.setResonance(0.7f);
        //filter.setMode(juce::dsp::LadderFilterMode::HPF24);

        lfo.initialise([](float x) { return std::sin(x); }, 128);
        lfo.setFrequency(3.0f);
        lfo2.initialise([](float x) { return std::sin(x); }, 128);
        lfo2.setFrequency(2.0f);
    }

    //==============================================================================
    void prepare (const juce::dsp::ProcessSpec& spec)
    {
        tempBlock = juce::dsp::AudioBlock<float> (heapBlock, spec.numChannels, spec.maximumBlockSize);
        processorChain.prepare (spec);

        lfo.prepare({spec.sampleRate / lfoUpdateRate, spec.maximumBlockSize, spec.numChannels});
        lfo2.prepare({ spec.sampleRate / lfoUpdateRate, spec.maximumBlockSize, spec.numChannels });
    }

    //==============================================================================
    void noteStarted() override
    {
        auto velocity = getCurrentlyPlayingNote().noteOnVelocity.asUnsignedFloat();
        auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();

        processorChain.get<osc1Index>().setFrequency(freqHz, true);
        processorChain.get<osc1Index>().setLevel(velocity);

        processorChain.get<osc2Index>().setFrequency(freqHz * 1.01f, true);
        processorChain.get<osc2Index>().setLevel(velocity);

        processorChain.get<osc3Index>().setFrequency(freqHz * .12f, true);
        processorChain.get<osc3Index>().setLevel(velocity);
    }

    //==============================================================================
    void notePitchbendChanged() override
    {
        auto freqHz = (float)getCurrentlyPlayingNote().getFrequencyInHertz();
        processorChain.get<osc1Index>().setFrequency(freqHz);
        processorChain.get<osc2Index>().setFrequency(freqHz * 1.01f);
        processorChain.get<osc3Index>().setFrequency(freqHz * .12f);
    }

    //==============================================================================
    void noteStopped (bool) override
    {
        clearCurrentNote();
    }

    //==============================================================================
    void notePressureChanged() override {}
    void noteTimbreChanged()   override {}
    void noteKeyStateChanged() override {}

    //==============================================================================
    //document code on lines 173- 201
    void renderNextBlock(juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
    {
        auto output = tempBlock.getSubBlock(0, (size_t)numSamples);
        output.clear();

        for (size_t pos = 0; pos < (size_t)numSamples;)
        {
            auto max = juce::jmin((size_t)numSamples - pos, lfoUpdateCounter);
            auto block = output.getSubBlock(pos, max);

            juce::dsp::ProcessContextReplacing<float> context(block);
            processorChain.process(context);

            pos += max;
            lfoUpdateCounter -= max;

            if (lfoUpdateCounter == 0)
            {
                lfoUpdateCounter = lfoUpdateRate;
                auto lfoOut = lfo.processSample(0.0f);                                 // [5]
                auto curoffFreqHz = juce::jmap(lfoOut, -1.0f, 1.0f, 100.0f, 2000.0f);  // [6]
                auto lfoOut2 = lfo2.processSample(0.0f);                                 // [5]
                auto curoffFreqHz2 = juce::jmap(lfoOut2, -1.0f, 1.0f, 100.0f, 2000.0f);
                processorChain.get<filterIndex>().setCutoffFrequencyHz(curoffFreqHz);  // [7]
                processorChain.get<filterIndex>().setResonance(curoffFreqHz2);      // [8]
            }
        }

        juce::dsp::AudioBlock<float>(outputBuffer)
            .getSubBlock((size_t)startSample, (size_t)numSamples)
            .add(tempBlock);
    }

private:
    //==============================================================================
    juce::HeapBlock<char> heapBlock;
    juce::dsp::AudioBlock<float> tempBlock;

    enum
    {
        osc1Index,
        osc2Index,
        osc3Index,
        filterIndex,
        masterGainIndex
    };

    juce::dsp::ProcessorChain<CustomOscillator<float>, CustomOscillator<float>, CustomOscillator<float>, juce::dsp::LadderFilter<float>, juce::dsp::Gain<float>> processorChain;

    static constexpr size_t lfoUpdateRate = 100;
    size_t lfoUpdateCounter = lfoUpdateRate;
    juce::dsp::Oscillator<float> lfo;
    juce::dsp::Oscillator<float> lfo2;
};

A few problems in the code :

You are multiplying the 3rd oscillator frequency by some odd factor that makes it several octaves lower/higher than the base frequency of the voice. Remember, you are dealing with Hz values at that point, not with musical notes. If you multiply by 0.5, the oscillator will play one octave lower, if by 2, it will be one octave higher etc. Negative values are not going to work at all.

Your resonance is still mapped to the incorrect range, you should do something like this instead :

auto curresonance = juce::jmap(lfoOut2, -1.0f, 1.0f, 0.0f, 0.95f);
processorChain.get<filterIndex>().setResonance(curresonance );
1 Like

That worked. Thanks, but I’m not sure I understand why it did.
What’s the deference in how the ranges work for Cut and Res?

auto curoffFreqHz = juce::jmap(lfoOut, -1.0f, 1.0f, 100.0f, 2000.0f); 
auto curresonance = juce::jmap(lfoOut2, -1.0f, 1.0f, 0.0f, .95f);

In the jmap the last pair of numbers determines the new range. For the filter cut off, the allowed range is something like 20 to 20000 Hz, but for the resonance the allowed range is from 0 to a bit under 1. The resonance isn’t a frequency parameter but a parameter that changes how the filter curve is shaped around the cut off frequency.

1 Like

Makes sense! Thanks for clarifying!
Very interesting stuff :slight_smile: