Equalizer plugin

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

I know at present it is possible to set up a system-wide equalizer by
using a LADSPA effect configured in .asoundrc, but (to my knowledge)
adjusting such a setup requires editing .asoundrc and then restarting
all applications using ALSA.  I've cooked up an external PCM plugin that
uses its own config file to store equalizer settings.  Since it watches
the config file with inotify, it is possible to change the settings
without restarting any applications.  I plan to eventually write a GUI
for it.  So ... is this useful?  Any suggestions?

-- John
// AEq -- Equalizer plugin for ALSA
// Copyright 2010 John Lindgren <john.lindgren@xxxxxxx>
//
// Implementation of a 10 band time domain graphic equalizer using IIR filters
// Copyright 2001 Anders Johansson <ajh@xxxxxxxxxxxxxxxxxx>
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, version 2.
//
// This program is distributed in the hope that it will be useful, but without
// any warranty; without even the implied warranty of merchantability or fitness
// for a particular purpose.  See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// this program.  If not, see <http://www.gnu.org/licenses/>.

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>

#include <alsa/asoundlib.h>
#include <alsa/pcm_external.h>

#define ERROR(...) fprintf (stderr, "AEq: " __VA_ARGS__)

#define BANDS 10
#define MAX_CHANS 10

// 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.224745

typedef struct {
   char path[PATH_MAX];
   int notify;
   int on;
   int chans, rate;
   float a[BANDS][2]; // A weights
   float b[BANDS][2]; // B weights
   float wq[MAX_CHANS][BANDS][2]; // Circular buffer for W data
   float gv[MAX_CHANS][BANDS]; // Gain factor for each channel and band
   int K; // Number of used eq bands
} AEQState;

static const float freqs[BANDS] = {31.25, 62.5, 125, 250, 500, 1000, 2000, 4000,
 8000, 16000};
static const char * const labels[BANDS] = {"31.25 Hz", "62.5 Hz", "125 Hz",
 "250 Hz", "500 Hz", "1 kHz", "2 kHz", "4 kHz", "8 kHz", "16 kHz"};

// 2nd order band-pass filter design
static void bp2 (float * a, float * b, float fc, float q) {
   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;
}

static void set_format (AEQState * s, int chans, int rate) {
   s->chans = chans;
   s->rate = rate;
   // Calculate number of active filters
   s->K = BANDS;
   while (freqs[s->K - 1] > (float) rate / 2.2)
      s->K --;
   // Generate filter taps
   for (int k = 0; k < s->K; k ++)
      bp2 (s->a[k], s->b[k], freqs[k] / (float) rate, Q);
   memset (& s->wq, 0, sizeof s->wq);
}

static void set_bands (AEQState * s, int on, const float bands[BANDS]) {
   s->on = on;
   for (int i = 0; i < MAX_CHANS; i ++) {
      for (int k = 0; k < BANDS; k ++)
         s->gv[i][k] = powf (10, bands[k] / 20) - 1;
   }
}

static void equalize (AEQState * s, int chan, const float * in, int in_step,
 float * out, int out_step, int samps) {
   // Gain factor
   const float * g = s->gv[chan];
   const float * end = in + in_step * samps;
   while (in < end) {
      float f = * in;
      in += in_step;
      for (int k = 0; k < s->K; k ++) {
         // Pointer to circular buffer wq
         float * wq = s->wq[chan][k];
         // Calculate output from AR part of current filter
         float w = f * s->b[k][0] + wq[0] * s->a[k][0] + wq[1] * s->a[k][1];
         // Calculate output from MA part of current filter
         f += (w + wq[1] * s->b[k][1]) * g[k];
         // Update circular buffer
         wq[1] = wq[0];
         wq[0] = w;
      }
      * out = f;
      out += out_step;
   }
}

static void read_config (const char * path, int * on, float bands[BANDS]) {
   * on = 0;
   memset (bands, 0, sizeof bands);
   FILE * file = fopen (path, "r");
   if (! file) {
      ERROR ("Failed to open %s for reading: %s\n", path, strerror (errno));
      return;
   }
   char line[256];
   if (! fgets (line, sizeof line, file) || ! sscanf (line, "%d", on))
      goto ERR;
   for (int k = 0; k < BANDS; k ++) {
      if (! fgets (line, sizeof line, file) || ! sscanf (line, "%f", bands + k))
         goto ERR;
   }
   fclose (file);
   return;
ERR:
   ERROR ("Syntax error in %s.\n", path);
   fclose (file);
}

static void write_config (const char * path, int on, const float bands[BANDS]) {
   FILE * file = fopen (path, "w");
   if (! file) {
      ERROR ("Failed to open %s for writing: %s\n", path, strerror (errno));
      return;
   }
   fprintf (file, "%d # On/off\n", on);
   for (int k = 0; k < BANDS; k ++)
      fprintf (file, "%.1f # %s\n", bands[k], labels[k]);
   fclose (file);
}

static int folder_init (const char * path) {
   if (! mkdir (path, S_IRUSR | S_IXUSR | S_IWUSR | S_IRGRP | S_IXGRP) || errno
    == EEXIST)
      return 1;
   ERROR ("Failed to create %s: %s.\n", path, strerror (errno));
   return 0;
}

static int file_exists (const char * path) {
   struct stat s;
   if (! lstat (path, & s))
      return 1;
   if (errno == ENOENT)
      return 0;
   ERROR ("Failed to stat %s: %s.\n", path, strerror (errno));
   return 0;
}

static int config_init (char * path, int psize) {
   snprintf (path, psize, "%s/.config", getenv ("HOME"));
   if (! folder_init (path))
      return 0;
   snprintf (path + strlen (path), psize - strlen (path), "/aeq");
   if (! folder_init (path))
      return 0;
   snprintf (path + strlen (path), psize - strlen (path), "/config");
   if (! file_exists (path)) {
      float bands[BANDS];
      memset (bands, 0, sizeof bands);
      write_config (path, 0, bands);
   }
   return 1;
}

static int notify_new (const char * path) {
   int n = inotify_init1 (IN_CLOEXEC | IN_NONBLOCK);
   if (n < 0) {
      ERROR ("Failed to start inotify: %s.\n", strerror (errno));
      return -1;
   }
   if (inotify_add_watch (n, path, IN_CLOSE_WRITE) < 0) {
      ERROR ("Failed to set inotify watch on %s: %s.\n", path, strerror (errno));
      close (n);
      return -1;
   }
   return n;
}

static int notify_changed (int n) {
   struct inotify_event e;
   int readed = read (n, & e, sizeof e);
   if (readed < 0) {
      if (errno != EAGAIN)
         ERROR ("Failed to read from inotify watch: %s.\n", strerror (errno));
      return 0;
   }
   return (readed > 0);
}

static snd_pcm_sframes_t aeq_transfer (snd_pcm_extplug_t * plug,
 const snd_pcm_channel_area_t * out_areas, snd_pcm_uframes_t out_off,
 const snd_pcm_channel_area_t * in_areas, snd_pcm_uframes_t in_off,
 snd_pcm_uframes_t frames) {
   AEQState * s = plug->private_data;
   if (notify_changed (s->notify)) {
      int on;
      float bands[BANDS];
      read_config (s->path, & on, bands);
      set_bands (s, on, bands);
   }
   if (s->on) {
      for (int i = 0; i < s->chans; i ++) {
         const float * in = (float *) in_areas[i].addr + (in_areas[i].first +
          in_areas[i].step * in_off) / (8 * sizeof (float));
         int in_step = in_areas[i].step / (8 * sizeof (float));
         float * out = (float *) out_areas[i].addr + (out_areas[i].first +
          out_areas[i].step * out_off) / (8 * sizeof (float));
         int out_step = out_areas[i].step / (8 * sizeof (float));
         equalize (s, i, in, in_step, out, out_step, frames);
      }
   } else
      snd_pcm_areas_copy (out_areas, out_off, in_areas, in_off, s->chans,
       frames, SND_PCM_FORMAT_FLOAT);
   return frames;
}

static int aeq_init (snd_pcm_extplug_t * plug) {
   AEQState * s = plug->private_data;
   if (plug->channels > MAX_CHANS)
      return -EINVAL;
   set_format (s, plug->channels, plug->rate);
   if (! config_init (s->path, sizeof s->path))
      return -EIO;
   if ((s->notify = notify_new (s->path)) < 0)
      return -EIO;
   int on;
   float bands[BANDS];
   read_config (s->path, & on, bands);
   set_bands (s, on, bands);
   return 0;
}

static int aeq_close (snd_pcm_extplug_t * plug) {
   AEQState * s = plug->private_data;
   close (s->notify);
   free (s);
   free (plug);
   return 0;
}

static const snd_pcm_extplug_callback_t aeq_callback = {
 .transfer = aeq_transfer,
 .init = aeq_init,
 .close = aeq_close};

static snd_config_t * get_slave (snd_config_t * self) {
   snd_config_iterator_t i, next;
   snd_config_for_each (i, next, self) {
      snd_config_t * n = snd_config_iterator_entry (i);
      const char * id;
      if (snd_config_get_id (n, & id) < 0)
         continue;
      if (! strcmp (id, "slave"))
         return n;
   }
   ERROR ("No slave PCM.\n");
   return NULL;
}

static snd_pcm_extplug_t * aeq_new (void) {
   snd_pcm_extplug_t * plug = calloc (1, sizeof (snd_pcm_extplug_t));
   if (! plug)
      return NULL;
   AEQState * s = calloc (1, sizeof (AEQState));
   if (! s) {
      free (plug);
      return NULL;
   }
   plug->version = SND_PCM_EXTPLUG_VERSION;
   plug->name = "AEq Equalizer";
   plug->callback = & aeq_callback;
   plug->private_data = s;
   return plug;
}

SND_PCM_PLUGIN_DEFINE_FUNC (aeq) {
   snd_config_t * slave = get_slave (conf);
   if (! slave)
      return -EINVAL;
   snd_pcm_extplug_t * plug = aeq_new ();
   if (! plug)
      return -ENOMEM;
   int err = snd_pcm_extplug_create (plug, name, root, slave, stream, mode);
   if (err < 0) {
      aeq_close (plug);
      return err;
   }
   snd_pcm_extplug_set_param (plug, SND_PCM_EXTPLUG_HW_FORMAT,
    SND_PCM_FORMAT_FLOAT);
   snd_pcm_extplug_set_slave_param (plug, SND_PCM_EXTPLUG_HW_FORMAT,
    SND_PCM_FORMAT_FLOAT);
   * pcmp = plug->pcm;
   return 0;
}

SND_PCM_PLUGIN_SYMBOL (aeq);
gcc -c -std=c99 -D_GNU_SOURCE -DPIC -Wall -Wextra -g -O2 -fpic `pkg-config --cflags alsa` aeq.c
gcc -shared `pkg-config --libs alsa` -Wl,-soname -Wl,libasound_module_pcm_aeq.so -o libasound_module_pcm_aeq.so aeq.o
_______________________________________________
Alsa-devel mailing list
Alsa-devel@xxxxxxxxxxxxxxxx
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

[Index of Archives]     [ALSA User]     [Linux Audio Users]     [Kernel Archive]     [Asterisk PBX]     [Photo Sharing]     [Linux Sound]     [Video 4 Linux]     [Gimp]     [Yosemite News]

  Powered by Linux