PrcoessorDuplicator and dB/Octave

When I implement dB/Octave, I generally have my processor chain set up as two separate mono chains; one for the left, and the other for the right.

Then I use some xxxxButterWorthMethod() call to get the coefficients.

How would I achieve the same results (e.g. applying different orders to the xxxButterWorthMethod calls to get different db/Octave) using ProcessorDuplicator?

same as Chain

for ease of use

// The filter and coefficients
using Filter = juce::dsp::IIR::Filter<float>;
using Coefficients = juce::dsp::IIR::Coefficients<float>;

// The processorDuplicator
using FilterCoeff = juce::dsp::ProcessorDuplicator<Filter, Coefficients>;

in your ProcessorChain

// Chain
juce::dsp::ProcessorChain<FilterCoeff> chain;

in your prepare func

// assuming you have all set up
chain.prepare(spec);

in your process

//assuming you have all set up
chain.process(context);

just place your ProcessorDuplicator in the ProcessorChain

auto lowcut = juce::dsp::IIR::Coefficients<float>::makeHighPass(sampleRate, /*FREQ*/, /*Q*/);
*/*DUPLICATOR STATE*/.state = *lowcut;

The MakeHighPass function you’re using has a last parameter of “Q” (Bandwidth, I believe).

It’s my understanding that to achieve db/Octave you need a call that uses “Order” as the last parameter.

HighPass Butterworth Filter

oh yes, ok… sorry. :slight_smile:
im no expert on the filters for juce BUT, you can create your own custom filters for examples, rather than using :slight_smile:

juce::dsp::IIR::Coefficients<float>::make{FILTER TYPE}(x, y, z);

you do it like

juce::dsp::IIR::Coefficients<float>::Coefficients(x, y, z, a);

the full list can be found in

juce_IIRFilter.h

where it includes

/** Directly constructs an object from the raw coefficients.
            Most people will want to use the static methods instead of this, but the
            constructor is public to allow tinkerers to create their own custom filters!
        */
        Coefficients (NumericType b0, NumericType b1,
                      NumericType a0, NumericType a1);

        Coefficients (NumericType b0, NumericType b1, NumericType b2,
                      NumericType a0, NumericType a1, NumericType a2);

        Coefficients (NumericType b0, NumericType b1, NumericType b2, NumericType b3,
                      NumericType a0, NumericType a1, NumericType a2, NumericType a3);

so if you know, yourself, how the filter you’re looking for is made, you can make it using custom coefficients. Hope this helps in some way!

This is good information, that I’ll definitely need to look into.

It doesn’t quite answer my question, but it’s still good information.

designIIRHighpassHighOrderButterworthMethod will return the coefficients that can be used for a mono channel processorChain.

I would like to know how to do the same if the filter is part of processorDuplicator

got it working, finally… ill delete the previous mis-information that i was attempting
the code is definitely not optimized but i can clean it later;

using State = juce::AudioProcessorValueTreeState;
using Filter = juce::dsp::IIR::Filter<float>;
using FilterDesign = juce::dsp::FilterDesign<float>;
using Coeff = juce::dsp::IIR::Coefficients<float>;
using Duplicate = juce::dsp::ProcessorDuplicator<Filter, Coeff>;

The way a butterworth filter works is basically applying multiple filters on top of eachother, so the way you implement this is

juce::dsp::ProcessorChain < Duplicate, Duplicate, Duplicate, Duplicate > chain;

now this, is for a maximum of 4 filters, you can remove or add more. depending on different orders.

    auto order = 2 * (p.filter_order + 1);
	auto coeff = FilterDesign::designIIRHighpassHighOrderButterworthMethod(p.filter_freq, sampleRate, order);

	chain.setBypassed<0>(true);
	chain.setBypassed<1>(true);
	chain.setBypassed<2>(true);
	chain.setBypassed<3>(true);

	switch (p.filter_order) 
	{
	case ORDER_12:
		*chain.get<0>().state = *coeff[0];
		chain.setBypassed<0>(false);
		break;
	case ORDER_24:
		*chain.get<0>().state = *coeff[0];
		chain.setBypassed<0>(false);

		*chain.get<1>().state = *coeff[1];
		chain.setBypassed<1>(false);
		break;
	case ORDER_36:
		*chain.get<0>().state = *coeff[0];
		chain.setBypassed<0>(false);

		*chain.get<1>().state = *coeff[1];
		chain.setBypassed<1>(false);

		*chain.get<2>().state = *coeff[2];
		chain.setBypassed<2>(false);
		break;
	case ORDER_48:
		*chain.get<0>().state = *coeff[0];
		chain.setBypassed<0>(false);

		*chain.get<1>().state = *coeff[1];
		chain.setBypassed<1>(false);

		*chain.get<2>().state = *coeff[2];
		chain.setBypassed<2>(false);

		*chain.get<3>().state = *coeff[3];
		chain.setBypassed<3>(false);
		break;
	}

by disabling the filters before we process, we can then run a quick check to see how many filters should be used and then enabled and set coefficients as we need.

the switch here is saying

if( we are using an order of 12dB we only want 1 filter )
if( we are using an order of 24dB we want 2 filters, so we set filter 1 and 2 to enabled and use the coefficents for the FilterDesign[0] with the first, and FilterDesign[1] with the second… and so on )


The order is

2 → multiply by → order(index in enum) → +1
so this works out to → 2 * (0 + 1) → 2 * (1 + 1) → …

the index in the enum is the index of the filter “StringArray” for db/octave in your parameter tree. If you need any more help, let me know :slight_smile:

here is the project on Github, un-optimized. Github Project

Goodness!!! You have put in the work, haven’t you!

The pattern you’re using seems to match the patterns I’ve seen used with Monochains.

It will take me a little time to test this, but I will definitely respond within the week.

Regardless of the outcome, thank you for all your effort.