Multiband Compressor?

Just came across this old thread. Related:

I -sort of- recreated the Soundgoodizer from FL Studio, which is basically a multiband compression preset:

1 Like

still needs polishing:

it’s actually stereo, but I forgot to change some names. BPF have 1/3 octave bandwidths, frequencies spaced 1/3 octave apart. too many to tune by hand, interesting to automate it somehow, or group them (3 or 4 together) if you want something ‘classical’

import("effect.lib");
import("filter.lib");
import("music.lib");

not(x) = abs(x - 1);
mute = not(checkbox("[0] mute [tooltip: Mute the whole band]"));
bypass(switch, block) = select2(switch, _, block);
bswitch = checkbox("[0] bypass [tooltip: Bypass the compressor, but not the bandpass filter]");

ratio = hslider("[9] Ratio [tooltip: Compression ratio]", 2, 1, 100, 1);
attack = hslider("[A] Attack (sec) [tooltip: Time before the compressor starts to kick in]", 0.012, 0, 1, 0.001);
release = hslider("[B] Release (sec) [tooltip: Time before the compressor releases the sound]", 1.25, 0, 10, 0.01);
safe = hslider("[6] Makeup-Threshold (db) [tooltip: Threshold correction, an anticlip measure]", 2, 0, 10, 0.1);

// Makeup gain sliders
makeup1 = hslider("[5] Makeup1 (db) [tooltip: Post amplification and threshold]", 13, -50, 50, 0.1);
makeup2 = hslider("[5] Makeup2 (db) [tooltip: Post amplification and threshold]", 10, -50, 50, 0.1);
makeup3 = hslider("[5] Makeup3 (db) [tooltip: Post amplification and threshold]", 4, -50, 50, 0.1);
makeup4 = hslider("[5] Makeup4 (db) [tooltip: Post amplification and threshold]", 8, -50, 50, 0.1);
makeup5 = hslider("[5] Makeup5 (db) [tooltip: Post amplification and threshold]", 11, -50, 50, 0.1);
makeup6 = hslider("[5] Makeup6 (db) [tooltip: Post amplification and threshold]", 14, -50, 50, 0.1);
makeup7 = hslider("[5] Makeup7 (db) [tooltip: Post amplification and threshold]", 16, -50, 50, 0.1);
makeup8 = hslider("[5] Makeup8 (db) [tooltip: Post amplification and threshold]", 18, -50, 50, 0.1);
makeup9 = hslider("[5] Makeup9 (db) [tooltip: Post amplification and threshold]", 20, -50, 50, 0.1);
makeup10 = hslider("[5] Makeup10 (db) [tooltip: Post amplification and threshold]", 22, -50, 50, 0.1);
makeup11 = hslider("[5] Makeup11 (db) [tooltip: Post amplification and threshold]", 24, -50, 50, 0.1);
makeup12 = hslider("[5] Makeup12 (db) [tooltip: Post amplification and threshold]", 26, -50, 50, 0.1);
makeup13 = hslider("[5] Makeup13 (db) [tooltip: Post amplification and threshold]", 28, -50, 50, 0.1);
makeup14 = hslider("[5] Makeup14 (db) [tooltip: Post amplification and threshold]", 30, -50, 50, 0.1);
makeup15 = hslider("[5] Makeup15 (db) [tooltip: Post amplification and threshold]", 32, -50, 50, 0.1);
makeup16 = hslider("[5] Makeup16 (db) [tooltip: Post amplification and threshold]", 34, -50, 50, 0.1);
makeup17 = hslider("[5] Makeup17 (db) [tooltip: Post amplification and threshold]", 36, -50, 50, 0.1);
makeup18 = hslider("[5] Makeup18 (db) [tooltip: Post amplification and threshold]", 38, -50, 50, 0.1);
makeup19 = hslider("[5] Makeup19 (db) [tooltip: Post amplification and threshold]", 40, -50, 50, 0.1);
makeup20 = hslider("[5] Makeup20 (db) [tooltip: Post amplification and threshold]", 42, -50, 50, 0.1);
makeup21 = hslider("[5] Makeup21 (db) [tooltip: Post amplification and threshold]", 44, -50, 50, 0.1);
makeup22 = hslider("[5] Makeup22 (db) [tooltip: Post amplification and threshold]", 46, -50, 50, 0.1);
makeup23 = hslider("[5] Makeup23 (db) [tooltip: Post amplification and threshold]", 48, -50, 50, 0.1);
makeup24 = hslider("[5] Makeup24 (db) [tooltip: Post amplification and threshold]", 50, -50, 50, 0.1);
makeup25 = hslider("[5] Makeup25 (db) [tooltip: Post amplification and threshold]", 52, -50, 60, 0.1);
makeup26 = hslider("[5] Makeup26 (db) [tooltip: Post amplification and threshold]", 54, -50, 60, 0.1);
makeup27 = hslider("[5] Makeup27 (db) [tooltip: Post amplification and threshold]", 56, -50, 60, 0.1);
makeup28 = hslider("[5] Makeup28 (db) [tooltip: Post amplification and threshold]", 58, -50, 60, 0.1);


// Define bandpass filters based on center frequencies and bandwidths
bandpass1 =  par(i, 2, bandpass(3, 25 - 5.8/2, 25 + 5.8/2));
bandpass2 =  par(i, 2, bandpass(3, 31.5 - 7.3/2, 31.5 + 7.3/2));
bandpass3 =  par(i, 2, bandpass(3, 40 - 9.2/2, 40 + 9.2/2));
bandpass4 =  par(i, 2, bandpass(3, 50 - 11.6/2, 50 + 11.6/2));
bandpass5 =  par(i, 2, bandpass(3, 63 - 14.5/2, 63 + 14.5/2));
bandpass6 =  par(i, 2, bandpass(3, 80 - 18.3/2, 80 + 18.3/2));
bandpass7 =  par(i, 2, bandpass(3, 100 - 23/2, 100 + 23/2));
bandpass8 =  par(i, 2, bandpass(3, 125 - 29/2, 125 + 29/2));
bandpass9 =  par(i, 2, bandpass(3, 160 - 37/2, 160 + 37/2));
bandpass10 = par(i, 2, bandpass(3, 200 - 46/2, 200 + 46/2));
bandpass11 = par(i, 2, bandpass(3, 250 - 58/2, 250 + 58/2));
bandpass12 = par(i, 2, bandpass(3, 315 - 73/2, 315 + 73/2));
bandpass13 = par(i, 2, bandpass(3, 400 - 92/2, 400 + 92/2));
bandpass14 = par(i, 2, bandpass(3, 500 - 116/2, 500 + 116/2));
bandpass15 = par(i, 2, bandpass(3, 630 - 145/2, 630 + 145/2));
bandpass16 = par(i, 2, bandpass(3, 800 - 183/2, 800 + 183/2));
bandpass17 = par(i, 2, bandpass(3, 1000 - 230/2, 1000 + 230/2));
bandpass18 = par(i, 2, bandpass(3, 1250 - 290/2, 1250 + 290/2));
bandpass19 = par(i, 2, bandpass(3, 1600 - 370/2, 1600 + 370/2));
bandpass20 = par(i, 2, bandpass(3, 2000 - 460/2, 2000 + 460/2));
bandpass21 = par(i, 2, bandpass(3, 2500 - 580/2, 2500 + 580/2));
bandpass22 = par(i, 2, bandpass(3, 3150 - 730/2, 3150 + 730/2));
bandpass23 = par(i, 2, bandpass(3, 4000 - 920/2, 4000 + 920/2));
bandpass24 = par(i, 2, bandpass(3, 5000 - 1160/2, 5000 + 1160/2));
bandpass25 = par(i, 2, bandpass(3, 6300 - 1450/2, 6300 + 1450/2));
bandpass26 = par(i, 2, bandpass(3, 8000 - 1830/2, 8000 + 1830/2));
bandpass27 = par(i, 2, bandpass(3, 10000 - 2300/2, 10000 + 2300/2));
bandpass28 = par(i, 2, bandpass(3, 12500 - 2900/2, 12500 + 2900/2));

// Makeup gains with smoothing and muting
makeupGain(bswitch, mute, makeup, safe) = mute * (not(bswitch) * (makeup - safe) : db2linear : smooth(0.999));

Makeup1 = makeupGain(bswitch, mute, makeup1, safe);
Makeup2 = makeupGain(bswitch, mute, makeup2, safe);
Makeup3 = makeupGain(bswitch, mute, makeup3, safe);
Makeup4 = makeupGain(bswitch, mute, makeup4, safe);
Makeup5 = makeupGain(bswitch, mute, makeup5, safe);
Makeup6 = makeupGain(bswitch, mute, makeup6, safe);
Makeup7 = makeupGain(bswitch, mute, makeup7, safe);
Makeup8 = makeupGain(bswitch, mute, makeup8, safe);
Makeup9 = makeupGain(bswitch, mute, makeup9, safe);
Makeup10 = makeupGain(bswitch, mute, makeup10, safe);
Makeup11 = makeupGain(bswitch, mute, makeup11, safe);
Makeup12 = makeupGain(bswitch, mute, makeup12, safe);
Makeup13 = makeupGain(bswitch, mute, makeup13, safe);
Makeup14 = makeupGain(bswitch, mute, makeup14, safe);
Makeup15 = makeupGain(bswitch, mute, makeup15, safe);
Makeup16 = makeupGain(bswitch, mute, makeup16, safe);
Makeup17 = makeupGain(bswitch, mute, makeup17, safe);
Makeup18 = makeupGain(bswitch, mute, makeup18, safe);
Makeup19 = makeupGain(bswitch, mute, makeup19, safe);
Makeup20 = makeupGain(bswitch, mute, makeup20, safe);
Makeup21 = makeupGain(bswitch, mute, makeup21, safe);
Makeup22 = makeupGain(bswitch, mute, makeup22, safe);
Makeup23 = makeupGain(bswitch, mute, makeup23, safe);
Makeup24 = makeupGain(bswitch, mute, makeup24, safe);
Makeup25 = makeupGain(bswitch, mute, makeup25, safe);
Makeup26 = makeupGain(bswitch, mute, makeup26, safe);
Makeup27 = makeupGain(bswitch, mute, makeup27, safe);
Makeup28 = makeupGain(bswitch, mute, makeup28, safe);

compressorMono(bandpass, makeup, push) = bandpass : bypass(bswitch, compressor_mono(ratio, -push, attack, release)) : *(makeup);

// Mono processing
gcomp1 = vgroup("[1] C1", compressorMono(bandpass1, Makeup1, makeup1));
gcomp2 = vgroup("[1] C2", compressorMono(bandpass2, Makeup2, makeup2));
gcomp3 = vgroup("[1] C3", compressorMono(bandpass3, Makeup3, makeup3));
gcomp4 = vgroup("[1] C4", compressorMono(bandpass4, Makeup4, makeup4));
gcomp5 = vgroup("[1] C5", compressorMono(bandpass5, Makeup5, makeup5));
gcomp6 = vgroup("[1] C6", compressorMono(bandpass6, Makeup6, makeup6));
gcomp7 = vgroup("[1] C7", compressorMono(bandpass7, Makeup7, makeup7));
gcomp8 = vgroup("[1] C8", compressorMono(bandpass8, Makeup8, makeup8));
gcomp9 = vgroup("[1] C9", compressorMono(bandpass9, Makeup9, makeup9));
gcomp10 = vgroup("[1] C10", compressorMono(bandpass10, Makeup10, makeup10));
gcomp11 = vgroup("[1] C11", compressorMono(bandpass11, Makeup11, makeup11));
gcomp12 = vgroup("[1] C12", compressorMono(bandpass12, Makeup12, makeup12));
gcomp13 = vgroup("[1] C13", compressorMono(bandpass13, Makeup13, makeup13));
gcomp14 = vgroup("[1] C14", compressorMono(bandpass14, Makeup14, makeup14));
gcomp15 = vgroup("[1] C15", compressorMono(bandpass15, Makeup15, makeup15));
gcomp16 = vgroup("[1] C16", compressorMono(bandpass16, Makeup16, makeup16));
gcomp17 = vgroup("[1] C17", compressorMono(bandpass17, Makeup17, makeup17));
gcomp18 = vgroup("[1] C18", compressorMono(bandpass18, Makeup18, makeup18));
gcomp19 = vgroup("[1] C19", compressorMono(bandpass19, Makeup19, makeup19));
gcomp20 = vgroup("[1] C20", compressorMono(bandpass20, Makeup20, makeup20));
gcomp21 = vgroup("[1] C21", compressorMono(bandpass21, Makeup21, makeup21));
gcomp22 = vgroup("[1] C22", compressorMono(bandpass22, Makeup22, makeup22));
gcomp23 = vgroup("[1] C23", compressorMono(bandpass23, Makeup23, makeup23));
gcomp24 = vgroup("[1] C24", compressorMono(bandpass24, Makeup24, makeup24));
gcomp25 = vgroup("[1] C25", compressorMono(bandpass25, Makeup25, makeup25));
gcomp26 = vgroup("[1] C26", compressorMono(bandpass26, Makeup26, makeup26));
gcomp27 = vgroup("[1] C27", compressorMono(bandpass27, Makeup27, makeup27));
gcomp28 = vgroup("[1] C28", compressorMono(bandpass28, Makeup28, makeup28));

process = (_,_)<: hgroup("", gcomp1, gcomp2, gcomp3, gcomp4, gcomp5, gcomp6, gcomp7, gcomp8, gcomp9, gcomp10, gcomp11, gcomp12, gcomp13, gcomp14, gcomp15, gcomp16, gcomp17, gcomp18, gcomp19, gcomp20, gcomp21, gcomp22, gcomp23, gcomp24, gcomp25, gcomp26, gcomp27, gcomp28) :>(_,_);

Does LPF.ar(LPF.ar…) yield a 24db/octave slope for the crossover? Also, is it possible to implement knee with variable slope (or hard knee/soft knee without variable slope) in this compressor design or does it take another design.

Yes, that’s precisely 24db/oct. I think a hard knee is straightforward with this design. But I think for a soft knee; you would need a hack and blend between uncompressed and compressed. Or rewrite the compressor code (untested to sketch an idea).

compressor = { |snd, attack, release, threshold, ratio, kneeWidth|
    var amplitudeDb, gainDb, kneeStart, kneeEnd, kneeGainDb, hardKneeGainDb, softKneeGainDb, gain;
    amplitudeDb = Amplitude.ar(snd, attack, release).ampdb;


    hardKneeGainDb = ((amplitudeDb - threshold) * (1 - (1 / ratio))).min(0);

    if (kneeWidth > 0) {

        kneeStart = threshold - (kneeWidth / 2);
        kneeEnd = threshold + (kneeWidth / 2);
        softKneeGainDb = Select.kr(amplitudeDb <= kneeStart, [
            hardKneeGainDb,
            (amplitudeDb - kneeStart).linlin(0, kneeWidth, 0, (amplitudeDb - kneeEnd) * (1 - (1 / ratio))).min(0)
        ]);

        kneeGainDb = softKneeGainDb.linlin(0, kneeWidth, hardKneeGainDb, softKneeGainDb);
    }  {
        kneeGainDb = hardKneeGainDb;
    };

    gainDb = kneeGainDb;
    gain = gainDb.dbamp;
    snd * gain;
};

By the way, gaps exist between the frequency zones due to how the frequency bands are split. The math is not quite right, or it was intended for demonstration or effect.

I did a 5-band compressor with a GUI as a little exercise. This is based on @nathan’s simple no-knee compressor design from another thread. The GUI has gain reduction meters for each band and adjustable attack, release, threshold, ratio and gain for each band. Crossover frequency can be adjusted with the mouse (normal: 1 Hz increments/decrements, with ‘shift’ held 100 Hz increments/decrements) or with text input (like any NumberBox). Feel free to improve on the design, e.g. adding knee to the compressor circuit.

(
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01-44_1.aiff");
d = (
	pars: [\atk, \rel, \thresh, \ratio, \gain],
	atk: { 0.01 }!5,
	rel: { 0.1 }!5,
	thresh: { -12 }!5,
	ratio: { 4 }!5,
	gain: { 1 }!5,
	freqs: [150, 500, 1500, 5000], // crossover frequencies
	specs: (atk: [0.005, 0.2], rel: [0.05, 0.5], thresh: [-80, 0], ratio: [1, 20], gain: [0, 2] ),
	masterGain: 1,
	play: { 
		Ndef(\multiComp).set(*[\atk, \rel, \thresh, \gain, \freqs, \masterGain].collect{|n|[n, d[n]]}.flat);
		d.changed(\multiComp)
	}
);

Ndef(\multiComp, {
	var atk = \atk.kr(0.01!5), rel = \rel.kr(0.1!5), thresh = \thresh.kr(-12!5), ratio = \ratio.kr(4!5);
	var gain = \gain.kr(1!5), freqs = \freqs.kr([300, 1000, 2000, 3200]), sigIn = In.ar(\bus.kr(0));
	var compressor = { |snd, attack, release, threshold, ratio|
		var amplitudeDb, gainDb;
		amplitudeDb = Amplitude.ar(snd, attack, release).ampdb;
		gainDb = ((amplitudeDb - threshold) * (1 / ratio - 1)).min(0);
		snd * gainDb.dbamp;
	};
	var sigs = [sigIn] ++ freqs.reverse.collect{|n, i| LPF.ar(LPF.ar(sigIn, n), n) };
	var bands = (freqs.size.collect{|i| sigs[i] - sigs[i + 1] } ++ [sigs.last]).reverse;
	var comp = bands.collect{|n, i| compressor.(n, atk[i], rel[i], thresh[i], ratio[i]) * gain[i] };
	5.do{|i| SendPeakRMS.kr(bands[i] - comp[i], 30, 0.1, "/redux", i) };
	ReplaceOut.ar(\bus.kr(0), comp.sum!2 * \masterGain.kr(1));
}).play;

~multiBandGui = {|ev|
	var v = View(nil, Rect(500, 0, 340, 820)).front.alwaysOnTop_(true);
	var update = {
		ev[\pars].do{|n, i|d[n].do{|m, j| { p[i][j].value = m }.defer } };
		ev[\freqs].do{|n, i| { p[6][i].value = n }.defer }
	};
	var p = { List.new }!7;
	v.decorator = FlowLayout(v.bounds).gap_(4@8);
	StaticText(v, 300@40).string_("Multi Band Compressor").font_(Font(\Arial, 18, true));
	v.decorator.nextLine;
	5.do{|i|
		StaticText(v, 60@20).string_("Band " ++ (i + 1)).font_(Font(\Arial, bold: true));
		ev[\pars].do{|n, j|
			p[j].add(EZKnob(v, label: n, controlSpec: ev[\specs][n].asSpec, layout: \vert2, action: {|knob| 
				d[n][i] = knob.();
				Ndef(\multiComp).set(n, d[n])
			}))
		};
		v.decorator.nextLine;
		StaticText(v, 60@20); // filler
		StaticText(v, 60@20).string_(\Reduction);
		p[5].add(LevelIndicator(v, 200@20).numSteps_(20).style_(\led));
		3.do{ v.decorator.nextLine };
	};
	StaticText(v, 60@20).string_("X-Over").font_(Font(\Arial, bold: true));
	4.do{ p[6].add(NumberBox(v, 60@20)) };
	update.();
	Ndef(\multiComp).addDependant(update);
	v.onClose_{ Ndef(\multiComp).removeDependant(update) };
	OSCdef(\redux, { |msg| { p[5][msg[2]].value = msg[3].ampdb.linlin(-80, 0, 0, 1) }.defer }, "/redux");
};
~multiBandGui.(d);
{ PlayBuf.ar(1, b, BufRateScale.kr(b), loop: 1) }.play;
)

// GUI can be closed and re-opened again by executing ~multiBandGui.(d);
// You can set values from the editor and update the GUI like this:

d[\atk] = { rrand(0.01, 0.2) }!5;
Ndef(\multiComp).set(\atk, d[\atk]).changed;
2 Likes

Cool. I haven’t tested my code. Does it sound as intended, or did I fuck up somewhere?

Thanks for sharing, I’ll check it out when possible

How did you choose and calculate the freqs and bw?

I haven’t actually tested your design yet, I will soon. I did a variation of @nathan’s model (can’t remember which thread is from). So basically create 4 signals with progressive lo-cuts and subtract the signals to get the isolated bands (see the Ndef above, var sigs, var bands for the exact implementation). I chose the starting crossover freqs semi-randomly as they can be adjusted from the GUI or by code. I am not sure about the optimal ranges for attack and release in the GUI (in code you can do whatever you want). Right now attack is in the range [0.005, 0.2] seconds and release is in the range [0.05, 0.5] seconds.

I imagine a perfect/sufficiently reconstructable filter bank would make sense for a multiband compressor. To be “perfect,” one would need to do some math, so in the process of parallelizing/serializer, the distance between frequencies and the curves of their bandwidth fill the spectrum in a “perfect” way.

(actually, there is a lot of theory in DSP about it, I’m simplifying a lot)

Like this:

But I also like to have the specs of classic vintage equipment. Those people did not kid with this stuff)))

I tried subtracting the sum of bands from the original sound, the resulting signal did not show up the meter, which is not the same as saying that they are totally identical but the difference was at least small enough not to show on the meter. If you have ways of making it ‘perfect’ I would be very interested, I don’t posses the math skills to improve this method myself. I like how multiband compression blurs the line between EQ and compression. For instance, one could adjust the gain of each band and the x-overs with a threshold of 0 to EQ the signal without compressing it. For this case (and in general) it is desirable that the sum of bands is equal (or very near equal) to the original.

For reference and while people are posting things, the multiband compressor I use. It’s still a work in progress, and I’m not really an expert on compressor design so … :woman_shrugging:

You probably can’t run it bc it has a lot of dependencies to private library stuff, but maybe useful to look at the signal chain if anyone is interested. It has both global compression settings and per-band ones, but I never really use it like that so i need to rethink that part a bit.

It does the think that a few compressors do where you set a min and max - below the min it expands, above the max it compresses, and in the middle it’s pass-thru.

1 Like

Still have not tested this code, but it should be very easy to add the knee control to my GUI design, I will mess around with. I just found this softknee compressor from @Wouter_Snoei. I have not tested it yet, but looking at the source code could probably give ideas about how to implement soft knee.

1 Like

The Faust example I posted here uses the specs of the Sonology BEA5 third-octave filter bank/matrix, I used this filters and loved it, (it is usually used grouping 3 or 4 bands together, not like I did, individually)

image

Do the missing parts interfere with something meaningful? You came up with the params by ear?

That’s an interesting code there. It works with RMS and uses other “mechanics” than my Snipett. Since I haven’t test mine, this one probably sounds way better))

Most of the missing stuff is GUI things, and the Pdef at the bottom - I THINK the only thing special in the SynthDef is SynthDef.channelized, which just generates version of the def for different channel counts.

To be totally honest, I did it partly by ear and partly just as a very utilitarian tool to control levels in the music that I’m making, which often has wildly out of control dynamics. I wanted some thing extremely neutral and generic to stick at the end of a signal chain - if I want something more colored or severe, that’s usually part of the sound design process and I’d do it elsewhere.

The matrixes of the filter I mentioned allow some very colored configuration/design. I will leave the manual for inspiration:

It’s possible to generate different synthdefs from a particular configuration of those matrixes, making this a fabulous candidate for a metaprogram synthdef generation code.

Or it may be possible just to use Faust; it will be less efficient but can work switching configurations, with the right skills.

I was messing around with my approach of bandsplitting the input (borrowed from Nathan, who also said this method is not 100% accurate). Here is how I tested, the difference is small, maybe be even negligible.

(
f = {
	var sigIn = WhiteNoise.ar(); // full scale signal
	var freqs = [5000, 2000, 500, 150];
	var sigs = [sigIn] ++ freqs.collect{|n, i| LPF.ar(LPF.ar(sigIn, n), n) };
	// sigs contain sigIn plus sigIn lowpassed at 5000, 2000, 500, 150 Hz;
	var bands = (freqs.size.collect{|i| sigs[i] - sigs[i + 1] } ++ [sigs.last]);
	// subtracting bands to get [5000-upperHzLim of SR, 2000-5000 hz, 500-2000 hz, 150-500 hz, plus the lowpassed at 150 Hz sig] 
	bands.sum - sigIn; // difference of the original signal and the sum the bands
};
f.plot(0.1)
)

c = f.asBuffer(0.1, action: {|buf| buf.loadToFloatArray(action: {|fa| fa.maxItem.debug(\maxDifference)  })}); 
// Max Difference approx 1.19e-07
1 Like

@Thor_Madsen

It’s not Rocket Science. Just follow the steps:

Calculating the center frequencies and bandwidths for each band-pass filter is necessary to create a precise filter bank with minimal gaps and overlaps. That’s how I do:

  1. For each pair of adjacent frequencies f_i and f_i+1, the center frequency f_c is the geometric mean.

Given Frequencies: Frequencies=[5000,2000,500,150,50], the geometric mean is:

  1. Bandwidths are:
BW_1 = 5000 - 2000 = 3000 
BW_2 = 2000 - 500 = 1500 
BW_3 = 500 - 150 = 350 
BW_4 = 150 - 50 = 100
  1. Q factors can also be calculated: [1.05, 0.67, 0.78, 0.87]

In this case, something like this would give such a filter bank:

freqs = [5000, 2000, 500, 150, 50];
centers = [3162.28, 1000, 273.86, 86.60];
bws = [3000, 1500, 350, 100];
qs = [1.05, 0.67, 0.78, 0.87];

But remember, DSP is sometimes very tricky. To get real about it, it would be necessary to do some study.

Also, it’s subjective. That’s why I am collecting the specs for the one I like.

This is the hardcore version: Simple Examples of Perfect Reconstruction

Also, I think most of those calculations are based on IIR filters (more complex behavior, analogs, and some digital – less common, but possible)

Commercial plugins mostly use FIR filters( non-recursive and without memory), but they may also use biquad IIR filters for high-quality EQs (may be the case with harrison mixbus, I would guess).

1 Like

If you’re up for using FIR filters, SignalBox offers a method for generating prefect reconstruction: Signal: *gaussianBank

3 Likes

28 posts were split to a new topic: Arbitrarily high-order IIR filters