EQ plugin project

This is some new content in the pipeline coming soon. This is going to be a tutorial covering the construction of a 3 band eq plugin from start to finish, hopefully ending in a simple but functional project.

Will be based on this public domain code I found while looking around musicdsp.org

The author states that this source can be used for whatever we want. This means that we’re free to redistribute and modify this source to our hearts content without fear of upsetting anyone. Definitely a nice starting point for a tutorial.

The source, as it stands, is just a function which processes a single floating point input. I’ll get converting this to a usable plugin, showing all of my thought processes along the way.

This first file presented below is the header file

//---------------------------------------------------------------------------
//
//                                3 Band EQ ๐Ÿ™‚
//
// EQ.H - Header file for 3 band EQ
//
// (c) Neil C / Etanza Systems / 2K6
//
// Shouts / Loves / Moans = etanza at lycos dot co dot uk
//
// This work is hereby placed in the public domain for all purposes, including
// use in commercial applications.
//
// The author assumes NO RESPONSIBILITY for any problems caused by the use of
// this software.
//
//----------------------------------------------------------------------------

#ifndef __EQ3BAND__
#define __EQ3BAND__

// ------------
//| Structures |
// ------------

typedef struct
{
  // Filter #1 (Low band)

  double  lf;       // Frequency
  double  f1p0;     // Poles ...
  double  f1p1;
  double  f1p2;
  double  f1p3;

  // Filter #2 (High band)

  double  hf;       // Frequency
  double  f2p0;     // Poles ...
  double  f2p1;
  double  f2p2;
  double  f2p3;

  // Sample history buffer

  double  sdm1;     // Sample data minus 1
  double  sdm2;     //                   2
  double  sdm3;     //                   3

  // Gain Controls

  double  lg;       // low  gain
  double  mg;       // mid  gain
  double  hg;       // high gain

} EQSTATE;

// ---------
//| Exports |
// ---------

extern void   init_3band_state(EQSTATE* es, int lowfreq, int highfreq, int mixfreq);
extern double do_3band(EQSTATE* es, double sample);

#endif // #ifndef __EQ3BAND__
//---------------------------------------------------------------------------

This next file presented below is the C source file:

//----------------------------------------------------------------------------
//
//                                3 Band EQ ๐Ÿ™‚
//
// EQ.C - Main Source file for 3 band EQ
//
// (c) Neil C / Etanza Systems / 2K6
//
// Shouts / Loves / Moans = etanza at lycos dot co dot uk
//
// This work is hereby placed in the public domain for all purposes, including
// use in commercial applications.
//
// The author assumes NO RESPONSIBILITY for any problems caused by the use of
// this software.
//
//----------------------------------------------------------------------------

// NOTES :
//
// - Original filter code by Paul Kellet (musicdsp.pdf)
//
// - Uses 4 first order filters in series, should give 24dB per octave
//
// - Now with P4 Denormal fix ๐Ÿ™‚

//----------------------------------------------------------------------------

// ----------
//| Includes |
// ----------

#include <math.h>
#include "eq.h"

// -----------
//| Constants |
// -----------

static double vsa = (1.0 / 4294967295.0);   // Very small amount (Denormal Fix)

// ---------------
//| Initialise EQ |
// ---------------

// Recommended frequencies are ...
//
//  lowfreq  = 880  Hz
//  highfreq = 5000 Hz
//
// Set mixfreq to whatever rate your system is using (eg 48Khz)

void init_3band_state(EQSTATE* es, int lowfreq, int highfreq, int mixfreq)
{
  // Clear state

  memset(es,0,sizeof(EQSTATE));

  // Set Low/Mid/High gains to unity

  es->lg = 1.0;
  es->mg = 1.0;
  es->hg = 1.0;

  // Calculate filter cutoff frequencies

  es->lf = 2 * sin(M_PI * ((double)lowfreq / (double)mixfreq));
  es->hf = 2 * sin(M_PI * ((double)highfreq / (double)mixfreq));
}

// ---------------
//| EQ one sample |
// ---------------

// - sample can be any range you like ๐Ÿ™‚
//
// Note that the output will depend on the gain settings for each band
// (especially the bass) so may require clipping before output, but you
// knew that anyway ๐Ÿ™‚

double do_3band(EQSTATE* es, double sample)
{
  // Locals

  double  l,m,h;      // Low / Mid / High - Sample Values

  // Filter #1 (lowpass)

  es->f1p0  += (es->lf * (sample   - es->f1p0)) + vsa;
  es->f1p1  += (es->lf * (es->f1p0 - es->f1p1));
  es->f1p2  += (es->lf * (es->f1p1 - es->f1p2));
  es->f1p3  += (es->lf * (es->f1p2 - es->f1p3));

  l          = es->f1p3;

  // Filter #2 (highpass)

  es->f2p0  += (es->hf * (sample   - es->f2p0)) + vsa;
  es->f2p1  += (es->hf * (es->f2p0 - es->f2p1));
  es->f2p2  += (es->hf * (es->f2p1 - es->f2p2));
  es->f2p3  += (es->hf * (es->f2p2 - es->f2p3));

  h          = es->sdm3 - es->f2p3;

  // Calculate midrange (signal - (low + high))

  m          = es->sdm3 - (h + l);

  // Scale, Combine and store

  l         *= es->lg;
  m         *= es->mg;
  h         *= es->hg;

  // Shuffle history buffer

  es->sdm3   = es->sdm2;
  es->sdm2   = es->sdm1;
  es->sdm1   = sample;

  // Return result

  return(l + m + h);
}

//----------------------------------------------------------------------------

  1. jeffyD
    January 2, 2010 at 19:37

    I recently started looking at doing VST plugins and needed a good basic low-mid-high freq splitter. Funny, the first code I found is the stuff you posted above! However, after messing around with it for a while, I have found it to be very unsatisfactory – the split results were falling apart in all three bands when the high split was around 8000hz or higher (maybe that’s why he suggests 5500 as the high cut point). Outside of that, seemed like a lot of frequency leakage into other bands even using his default freq’s. I tried other things on that site and none of them were really working for me.

    Looking around some more, I found some code by Christian Budde, here (well, follow the links to the open source stuff he’s got): http://www.savioursofsoul.de/Christian/vst-plugins/effect-plugins/

    Unfortunately, his code is all in pascal. However, at great pains to myself, I converted everything needed to get his 4th order linkwitz / riley crossover (http://en.wikipedia.org/wiki/Linkwitz-Riley_filter) implementation compiling in c++ and his stuff actually works – it splits a signal into 3 arbitrary bands quite well.

    Even better, it would be just a few simple lines of code to extend it to split it into more than 3 freq bands.

    Anyway, it’s too long to post here but I can pass it along if you like. Can’t say it’s the *best* stuff out there but it’s easily the best I’ve found so far and I have found it to be significantly better than what is posted here. It’s also super easy to use.

    • January 4, 2010 at 09:55

      Thanks for the response! I haven’t looked into this for a while but I was planning on getting these tutorials moving again after Easter. I haven’t really analysed the above (the one I posted) algorithm much yet, but after hearing your concerns I will have a play with it in Matlab. If you’ve got some source for something better, you can post it as a comment and then I can repost it as another page in source code format.

      Thanks again!

      • jeffyD
        January 5, 2010 at 01:35

        I took this code: http://www.musicdsp.org/archive.php?classid=3#266

        And turned it into a basic C++ class. Not sure how the code will be formatted but I’ll try it anyway. ๐Ÿ™‚

        //———————————————————
        class CLinkwtizRiley_4thOrder
        {
        public:

        CLinkwtizRiley_4thOrder(const double sampleRate=44000.0,
        const double crossoverFreq=1000.0) :
        mSampleRate(sampleRate), mCrossoverFreq(crossoverFreq),
        mXm1(0.0), mXm2(0.0), mXm3(0.0), mXm4(0.0),
        mLYm1(0.0), mLYm2(0.0), mLYm3(0.0), mLYm4(0.0),
        mHYm1(0.0), mHYm2(0.0), mHYm3(0.0), mHYm4(0.0)
        {
        CalculateCoefficients();
        }

        void SetCrossoverFreq(const double newCrossoverFreq) {
        mCrossoverFreq = newCrossoverFreq;
        CalculateCoefficients();
        }
        void SetSampleRate(const double newSampleRate) {
        mSampleRate = newSampleRate;
        CalculateCoefficients();
        }

        void ProcessSample(const double &sample, double &lowOut, double &highOut) {
        static double smp; // done in case sample is coming in as a reused var in the outs
        smp = sample;
        lowOut = mL_A0 * smp + mL_A1 * mXm1 + mL_A2 * mXm2 + mL_A3 * mXm3 + mL_A4 * mXm4
        – mB1 * mLYm1 – mB2 * mLYm2 – mB3 * mLYm3 – mB4 * mLYm4;

        highOut = mH_A0 * smp + mH_A1 * mXm1 + mH_A2 * mXm2 + mH_A3 * mXm3 + mH_A4 * mXm4
        – mB1 * mHYm1 – mB2 * mHYm2 – mB3 * mHYm3 – mB4 * mHYm4;
        // Shuffle history
        mXm4 = mXm3; mXm3 = mXm2; mXm2 = mXm1; mXm1 = smp;
        mLYm4 = mLYm3; mLYm3 = mLYm2; mLYm2 = mLYm1; mLYm1 = lowOut; // low
        mHYm4 = mHYm3; mHYm3 = mHYm2; mHYm2 = mHYm1; mHYm1 = highOut;// high
        }

        private:

        void CalculateCoefficients() {
        // shared for both lp, hp; optimizations here
        double wc = 2.0 * M_PI * mCrossoverFreq;
        double wc2 = wc * wc;
        double wc3 = wc2 * wc;
        double wc4 = wc2 * wc2;
        double k = wc / tan(M_PI * mCrossoverFreq / mSampleRate);
        double k2=k * k;
        double k3=k2 * k;
        double k4=k2 * k2;
        double sqrt2 = sqrt(2.0);
        double sq_tmp1 = sqrt2 * wc3 * k;
        double sq_tmp2 = sqrt2 * wc * k3;
        double a_tmp = 4.0 * wc2 * k2 + 2.0 * sq_tmp1 + k4 + 2.0 * sq_tmp2 + wc4;

        // Shared coeff
        mB1 = (4.0 * (wc4 + sq_tmp1 – k4 – sq_tmp2)) / a_tmp;
        mB2 = (6.0 * wc4 – 8.0 * wc2 * k2 + 6.0 * k4) / a_tmp;
        mB3 = (4.0 * (wc4 – sq_tmp1 + sq_tmp2 – k4)) / a_tmp;
        mB4 = (k4 – 2.0 * sq_tmp1 + wc4 – 2.0 * sq_tmp2 + 4.0 * wc2 * k2) / a_tmp;

        // low-pass coeff
        mL_A0 = wc4 / a_tmp;
        mL_A1 = 4.0 * wc4 / a_tmp;
        mL_A2 = 6.0 * wc4 / a_tmp;
        mL_A3 = mL_A1;
        mL_A4 = mL_A0;

        // high-pass coeff
        mH_A0 = k4 / a_tmp;
        mH_A1 = -4.0 * k4 / a_tmp;
        mH_A2 = 6.0 * k4 / a_tmp;
        mH_A3 = mH_A1;
        mH_A4 = mH_A0;
        }

        double mSampleRate;
        double mCrossoverFreq;

        double mB1, mB2, mB3, mB4; // shared with lp & hp
        double mL_A0, mL_A1, mL_A2, mL_A3, mL_A4; // lp coefficients
        double mH_A0, mH_A1, mH_A2, mH_A3, mH_A4; // hp coefficients

        double mXm1, mXm2, mXm3, mXm4; // incoming sample history
        double mLYm1, mLYm2, mLYm3, mLYm4; // low output history
        double mHYm1, mHYm2, mHYm3, mHYm4; // high output history
        };

        As for how to use it, pretty easy:

        // I just allocate two of them with the desired crossover points
        mLow = new CLinkwtizRiley_4thOrder(44000.0, 400.0);
        mHigh = new CLinkwtizRiley_4thOrder(44000.0, 4000.0);

        // Then take your sample, pass it into the low, then take the high from that and pass it through the high crossover
        double low, mid, high;

        mLow->ProcessSample(sampleIn, low, high);
        mHigh->ProcessSample(high, mid, high);

        When I sum the low mid and high back together, I can’t discern any signal loss once I bypass the splitter…

        The only bad thing I’ve noticed is that changing the low freq real time is unstable. There are probably ways around this…

        Good luck!

  2. January 5, 2010 at 10:21

    Thanks JeffyD! When I get some time to carry on with this in the spring then I’ll analyse both solutions and see what comes out. Thanks again for your efforts and I’m sure others will find your contributions useful.

  3. February 7, 2010 at 16:25

    Dear All

    We our looking for anyone that can help and advise us on making our own vst sampler. We have all our own original drum samples ready but not sure on what programs to code it with and where to learn the code.

    admin@original-music.co.uk

    • February 8, 2010 at 17:44

      Your best bet is to go to the developer section of the KVR formus. You can ask for general assistance, or someone may even wish to be contracted for the task.

  4. moog
    April 12, 2010 at 21:55

    We are waiting for the next part of this tutorial :).
    It’s great that you want to show us the way of building EQ from A to Z!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: