diff options
Diffstat (limited to 'src/libaudcore/equalizer.cc')
-rw-r--r-- | src/libaudcore/equalizer.cc | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/src/libaudcore/equalizer.cc b/src/libaudcore/equalizer.cc new file mode 100644 index 0000000..ebaa438 --- /dev/null +++ b/src/libaudcore/equalizer.cc @@ -0,0 +1,213 @@ +/* + * equalizer.c + * Copyright 2001 Anders Johansson + * Copyright 2010-2011 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +/* + * Anders Johansson prefers float *ptr; formatting. Please keep it that way. + * - tallica + */ + +#include "equalizer.h" +#include "internal.h" + +#include <assert.h> +#include <math.h> +#include <pthread.h> +#include <string.h> + +#include "audio.h" +#include "audstrings.h" +#include "hook.h" +#include "runtime.h" + +/* Q value for band-pass filters 1.2247 = (3/2)^(1/2) + * Gives 4 dB suppression at Fc*2 and Fc/2 */ +#define Q 1.2247449 + +/* Center frequencies for band-pass filters (Hz) */ +/* These are not the historical WinAmp frequencies, because the IIR filters used + * here are designed for each frequency to be twice the previous. Using WinAmp + * frequencies leads to too much gain in some bands and too little in others. */ +static const float CF[AUD_EQ_NBANDS] = {31.25, 62.5, 125, 250, 500, 1000, 2000, + 4000, 8000, 16000}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static bool active; +static int channels, rate; +static float a[AUD_EQ_NBANDS][2]; /* A weights */ +static float b[AUD_EQ_NBANDS][2]; /* B weights */ +static float wqv[AUD_MAX_CHANNELS][AUD_EQ_NBANDS][2]; /* Circular buffer for W data */ +static float gv[AUD_MAX_CHANNELS][AUD_EQ_NBANDS]; /* Gain factor for each channel and band */ +static int K; /* Number of used EQ bands */ + +/* 2nd order band-pass filter design */ +static void bp2 (float *a, float *b, float fc) +{ + float th = 2 * M_PI * fc; + float C = (1 - tanf (th * Q / 2)) / (1 + tanf (th * Q / 2)); + + a[0] = (1 + C) * cosf (th); + a[1] = -C; + b[0] = (1 - C) / 2; + b[1] = -1.005; +} + +void eq_set_format (int new_channels, int new_rate) +{ + pthread_mutex_lock (& mutex); + + channels = new_channels; + rate = new_rate; + + /* Calculate number of active filters: the center frequency must be less + * than rate/2Q to avoid singularities in the tangent used in bp2() */ + K = AUD_EQ_NBANDS; + + while (K > 0 && CF[K - 1] > (float) rate / (2.005 * Q)) + K --; + + /* Generate filter taps */ + for (int k = 0; k < K; k ++) + bp2 (a[k], b[k], CF[k] / (float) rate); + + /* Reset state */ + memset (wqv[0][0], 0, sizeof wqv); + + pthread_mutex_unlock (& mutex); +} + +static void eq_set_bands_real (double preamp, double *values) +{ + float adj[AUD_EQ_NBANDS]; + + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + adj[i] = preamp + values[i]; + + for (int c = 0; c < AUD_MAX_CHANNELS; c ++) + { + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + gv[c][i] = pow (10, adj[i] / 20) - 1; + } +} + +void eq_filter (float *data, int samples) +{ + int channel; + + pthread_mutex_lock (& mutex); + + if (! active) + { + pthread_mutex_unlock (& mutex); + return; + } + + for (channel = 0; channel < channels; channel ++) + { + float *g = gv[channel]; /* Gain factor */ + float *end = data + samples; + float *f; + + for (f = data + channel; f < end; f += channels) + { + int k; /* Frequency band index */ + float yt = *f; /* Current input sample */ + + for (k = 0; k < K; k ++) + { + /* Pointer to circular buffer wq */ + float *wq = wqv[channel][k]; + /* Calculate output from AR part of current filter */ + float w = yt * b[k][0] + wq[0] * a[k][0] + wq[1] * a[k][1]; + + /* Calculate output from MA part of current filter */ + yt += (w + wq[1] * b[k][1]) * g[k]; + + /* Update circular buffer */ + wq[1] = wq[0]; + wq[0] = w; + } + + /* Calculate output */ + *f = yt; + } + } + + pthread_mutex_unlock (& mutex); +} + +static void eq_update (void *data, void *user) +{ + pthread_mutex_lock (& mutex); + + active = aud_get_bool (nullptr, "equalizer_active"); + + double values[AUD_EQ_NBANDS]; + aud_eq_get_bands (values); + eq_set_bands_real (aud_get_double (nullptr, "equalizer_preamp"), values); + + pthread_mutex_unlock (& mutex); +} + +void eq_init (void) +{ + eq_update (nullptr, nullptr); + hook_associate ("set equalizer_active", eq_update, nullptr); + hook_associate ("set equalizer_preamp", eq_update, nullptr); + hook_associate ("set equalizer_bands", eq_update, nullptr); +} + +void eq_cleanup (void) +{ + hook_dissociate ("set equalizer_active", eq_update); + hook_dissociate ("set equalizer_preamp", eq_update); + hook_dissociate ("set equalizer_bands", eq_update); +} + +EXPORT void aud_eq_set_bands (const double values[AUD_EQ_NBANDS]) +{ + StringBuf string = double_array_to_str (values, AUD_EQ_NBANDS); + aud_set_str (nullptr, "equalizer_bands", string); +} + +EXPORT void aud_eq_get_bands (double values[AUD_EQ_NBANDS]) +{ + memset (values, 0, sizeof (double) * AUD_EQ_NBANDS); + String string = aud_get_str (nullptr, "equalizer_bands"); + str_to_double_array (string, values, AUD_EQ_NBANDS); +} + +EXPORT void aud_eq_set_band (int band, double value) +{ + assert (band >= 0 && band < AUD_EQ_NBANDS); + + double values[AUD_EQ_NBANDS]; + aud_eq_get_bands (values); + values[band] = value; + aud_eq_set_bands (values); +} + +EXPORT double aud_eq_get_band (int band) +{ + assert (band >= 0 && band < AUD_EQ_NBANDS); + + double values[AUD_EQ_NBANDS]; + aud_eq_get_bands (values); + return values[band]; +} |