1. LICENSE.
2. ACKNOWLEDGEMENTS.
3. DFT NUANCES AND THE EQUALIZER.
4. QUICK INTRODUCTION TO PerlPreProcessor.
5. CONFIGURATION AND HACKING.
6. BUILDING.
7. FILE LIST.



1. LICENSE.
-----------

The multichannel multiband FFT-based equalizer (currently mbeq_119700) is
released under the terms of GPL.

That's because the program is linked with FFTW which is also GPL.

However, if someone modifies the program to work (to be linked) with
LGPL-compatible FFT, then the derived program can be released under the terms
of LGPL.

Yet another entertaining idea is to implement an FFTW server which
would still be GPL. The plugins using the server can have whatever license,
that's because they won't be linked with the FFT server, they will just be its
clients.

Latency shouldn't be an issue since the plugin already has it own latency of
about 0.1 seconds because of DFT nature.



2. ACKNOWLEDGEMENTS.
--------------------

The author wants to thank FOSS movement as a whole, creators of FFTW and
Steve Harris personally.

Steve had patience to answer a lot of my questions regarding his mbeq_1197
plugin; this plugin was inspired by Steve's mbeq_1197.



3. DFT NUANCES AND THE EQUALIZER.
---------------------------------

Person dealing with any DFT equalizer should well understand DFT/FFT features
and limitations.

It is higly recommended to read the following documents:

http://www.hpc.sfu.ca/bugaboo/fftw/fftw_3.html#SEC16
http://www-cecpv.u-strasbg.fr/Documentations/FFTW/v3.0.1/What-FFTW-Really-Computes.html#What%20FFTW%20Really%20Computes
http://www-cecpv.u-strasbg.fr/Documentations/FFTW/v3.0.1/The-1d-Discrete-Fourier-Transform--DFT-.html#The%201d%20Discrete%20Fourier%20Transform%20(DFT)
http://www-cecpv.u-strasbg.fr/Documentations/FFTW/v3.0.1/The-1d-Real-data-DFT.html#The%201d%20Real-data%20DFT
.
One of the main issues is DFT/FFT spectral resolution. For N point DFT with
sampling frequency Fs spectral resolution is (Fs / N).

The plugin the way it is released has N = 4096, so for audio CD sampling
frequency of 44100Hz spectral resolution is (44100 / 4096)Hz = 10.766602Hz.

The existence of spectral resolution prevents end user from having
arbitrary central band frequencies in DFT-based equalizers, central frequencies
can only be a multiple of spectral resolution.

The target central frequencies of the released code are (in Hz):

20
28.2842712474619
40
50.3968419957949
63.496042078728
80
100.79368399159
126.992084157456
160
201.58736798318
253.984168314912
320
403.174735966359
507.968336629824
640
806.349471932719
1015.93667325965
1280
1612.69894386544
2031.8733465193
2560
3225.39788773088
4063.74669303859
5120
6450.79577546175
8127.49338607718
10240
12901.5915509235
16254.9867721544
20480
.

However, actual central frequencies will be somewhat different because of the
spectral resolution issue, here they are printed to STDERR by the plugin
during initialization:

instantiateMChMBEq :INFO: actual 00 band bin number: 2 frequency: 21.5332Hz
instantiateMChMBEq :INFO: actual 01 band bin number: 3 frequency: 32.2998Hz
instantiateMChMBEq :INFO: actual 02 band bin number: 4 frequency: 43.0664Hz
instantiateMChMBEq :INFO: actual 03 band bin number: 5 frequency: 53.833Hz
instantiateMChMBEq :INFO: actual 04 band bin number: 6 frequency: 64.5996Hz
instantiateMChMBEq :INFO: actual 05 band bin number: 7 frequency: 75.3662Hz
instantiateMChMBEq :INFO: actual 06 band bin number: 9 frequency: 96.8994Hz
instantiateMChMBEq :INFO: actual 07 band bin number: 12 frequency: 129.199Hz
instantiateMChMBEq :INFO: actual 08 band bin number: 15 frequency: 161.499Hz
instantiateMChMBEq :INFO: actual 09 band bin number: 19 frequency: 204.565Hz
instantiateMChMBEq :INFO: actual 10 band bin number: 24 frequency: 258.398Hz
instantiateMChMBEq :INFO: actual 11 band bin number: 30 frequency: 322.998Hz
instantiateMChMBEq :INFO: actual 12 band bin number: 37 frequency: 398.364Hz
instantiateMChMBEq :INFO: actual 13 band bin number: 47 frequency: 506.03Hz
instantiateMChMBEq :INFO: actual 14 band bin number: 59 frequency: 635.229Hz
instantiateMChMBEq :INFO: actual 15 band bin number: 75 frequency: 807.495Hz
instantiateMChMBEq :INFO: actual 16 band bin number: 94 frequency: 1012.06Hz
instantiateMChMBEq :INFO: actual 17 band bin number: 119 frequency: 1281.23Hz
instantiateMChMBEq :INFO: actual 18 band bin number: 150 frequency: 1614.99Hz
instantiateMChMBEq :INFO: actual 19 band bin number: 189 frequency: 2034.89Hz
instantiateMChMBEq :INFO: actual 20 band bin number: 238 frequency: 2562.45Hz
instantiateMChMBEq :INFO: actual 21 band bin number: 300 frequency: 3229.98Hz
instantiateMChMBEq :INFO: actual 22 band bin number: 377 frequency: 4059.01Hz
instantiateMChMBEq :INFO: actual 23 band bin number: 476 frequency: 5124.9Hz
instantiateMChMBEq :INFO: actual 24 band bin number: 599 frequency: 6449.19Hz
instantiateMChMBEq :INFO: actual 25 band bin number: 755 frequency: 8128.78Hz
instantiateMChMBEq :INFO: actual 26 band bin number: 951 frequency: 10239Hz
instantiateMChMBEq :INFO: actual 27 band bin number: 1198 frequency: 12898.4Hz
instantiateMChMBEq :INFO: actual 28 band bin number: 1510 frequency: 16257.6Hz
instantiateMChMBEq :INFO: actual 29 band bin number: 1902 frequency: 20478.1Hz
.

The target frequencies were meant to be 1/2-octave spaced in the (20..40)Hz
inclusive range, and 1/3-octave spaced above 40Hz. Because of spectral
resolution it is impossible to have better than 1/2-octave resolution below
40Hz, and even 1/2-octave resolution, strictly saying, is not possible for
the given 44100Hz / 4096 pair.

If less than spectral resolution separated frequencies are produced
by the Perl code of the plugin, then at run time (during initialization
stage) an error message will be issued and the plugin will 'exit(1)'.

That is because bin amplitudes are interpolated between the central
frequencies, the interpolation is linear, and it is impossible to calculate
angular coefficient for two points with the same X (frequency) coordinates.


Another important issue is windowing/oversampling/overlapping.

DFT by itself does not work well in case input signal contains frequencies
which are not multiples of spectral resolution, so to diminish negative effects
practical DFT applications run DFT on overlapping windowed arrays of input
samples.

The released plugin uses 1/2 overlap, and Steve Harris' mbeq_1197 uses
3/4 overlap, but with smaller N.

The released plugin uses Hannimg window - see, for example

http://astronomy.swin.edu.au/~pbourke/other/windows/#hanning
.

The code can easily be changed to implement Hamming window.


It is not that easy (though still easy) to implement a different
overlapping/oversampling.

See Steve Harris' mbeq_1197 for reference, and I have proven mathematically
that the windowing used in it is correct from the point of view invariancy
of direct FFT -> inverse FTT sequence (it is not that obvious at first sight).


Yet another issue is so called "bin 0" or DC component of DFT.

The DC component is the average of all N input samples.

If input signal is a periodic function with (N / Fs) period, then
the DC component is really DC, i.e. it does not change with time.

In reality it is not the case.

It might make sense to feed input signal through a high order IIR HPF to
get rid of all the components below (Fs / N).

And the related issue is whether user controls should or should not change
the amplitude of DC. The released plugin does not change it.


It should be understood that typically FFT comlexity is N * log(N).

So, increasing N by the factor of 2 apparently means complexity of
2 * N * log (2 * N). Luckily, real CPU load increases only as N * log (2 * N),
not as 2 * N * log (2 * N) - that's because N -> 2 * N change means that
the frequency with which FFT should be run is only half of the case of
N = 1 * N.



4. QUICK INTRODUCTION TO PerlPreProcessor.
------------------------------------------

A simple example:

"
[256] 16:17 sergei@athlon.home.net:/ibm/home/sergei/swh-plugins/swh-plugins-0.4.11> cat hello.c.before_PerlPreProcessor                                      
#include <stdio.h>

main()
  {
  // PERL_BEGIN
  foreach my $name(qw(Peter Paul Mary))
    {
    print <<EOD
  puts("Hello, $name !");
EOD
    ;
    } # foreach my $name(qw(Peter Paul Mary))
  // PERL_END
  }
[257] 16:17 sergei@athlon.home.net:/ibm/home/sergei/swh-plugins/swh-plugins-0.4.11> \rm -r .PerlPreProcWS/                                                   
[258] 16:17 sergei@athlon.home.net:/ibm/home/sergei/swh-plugins/swh-plugins-0.4.11> ./PerlPreProcessor -config_hash_file PerlPreProcessorConfigHash.c++.prl h
ello.c.before_PerlPreProcessor > hello.c 
PerlPreProcessor - Copyright 2001 Sergei Steshenko. This program is free software (GNU General Public License).
PerlPreProcessor : INFO : BEGINNING OF processing files from command line
executing => \mkdir -p .PerlPreProcWS
PerlPreProcessor : INFO : STARTED  processing 'hello.c.before_PerlPreProcessor' file
PerlPreProcessor : INFO : FINISHED processing 'hello.c.before_PerlPreProcessor' file
PerlPreProcessor : INFO : END OF       processing files from command line
[259] 16:17 sergei@athlon.home.net:/ibm/home/sergei/swh-plugins/swh-plugins-0.4.11> cat hello.c
#include <stdio.h>

main()
  {
  // PERL_BEGIN
  puts("Hello, Peter !");
  puts("Hello, Paul !");
  puts("Hello, Mary !");
  // PERL_END
  }
[260] 16:18 sergei@athlon.home.net:/ibm/home/sergei/swh-plugins/swh-plugins-0.4.11> gcc hello.c -o hello
[261] 16:18 sergei@athlon.home.net:/ibm/home/sergei/swh-plugins/swh-plugins-0.4.11> ./hello
Hello, Peter !
Hello, Paul !
Hello, Mary !
[262] 16:18 sergei@athlon.home.net:/ibm/home/sergei/swh-plugins/swh-plugins-0.4.11> 
".

One can see that PerlPreProcessor inserts text generated by Perl code between
BEGIN - END markers into the osurce code. The "text generated by Perl code"
words should be more precisely understood as STDOUT output of the Perl code.


So, in more general words, one can have text in whatever "native" language
(C/C++, Perl, Verilog, HTML, etc.) and the can want Perl to generate pieces of
code in that same native language. If so, the one should just insert the
corresponding markers (which should be comments in the native language) and
write Perl code between the markers.

The markers are configurable - see, for example,
PerlPreProcessorConfigHash.c++.prl file.

PerlPreProcessor can also perform stateless and stateful unlimitedly nested
'include; operations.



5. CONFIGURATION AND HACKING.
-----------------------------

In this context "configuration" means changing constants and "hacking"
means changing (mostly "C") code.

Configuration was meant to mainly be performed as making changes in the first
piece of Perl code:

// PERL_BEGIN

my ($_this_file_name, $_this_file_full_path, $_this_file_suffix) =
&File::Basename::fileparse($__global_input_file, '.c.before_PerlPreProcessor');

#warn "\$_this_file_name=$_this_file_name \$_this_file_full_path=$_this_file_full_path \$_this_file_suffix=$_this_file_suffix";

($MBEQ::__config_hash{UniqueID}) = ($_this_file_name =~ m/_(\d+)$/);
#warn "\$MBEQ::__config_hash{UniqueID}=$MBEQ::__config_hash{UniqueID}";

$MBEQ::__config_hash{bands_sub} =
sub
  {
  my $min_frequency = 20;
  my $max_frequency = 21050;

  my @frequencies;
  
  my $frequency = $min_frequency;
  push @frequencies, $frequency;
  
  $frequency = $min_frequency * sqrt(2);

  push @frequencies, $frequency;

  $frequency = $min_frequency * 2;

  push @frequencies, $frequency;
  

  my $ctr = 1;
  for(;;)
    {
    $frequency *= (2 ** (1/3));

    if($frequency >= $max_frequency)
      {
      last;
      }

    push @frequencies, $frequency;
    } # while($frequency < $max_frequency)

  warn "\@frequencies=@frequencies";

  @frequencies;
  };

$MBEQ::__config_hash{Label} = 'MChMBEq';
$MBEQ::__config_hash{FileLabel} = $_this_file_name;
$MBEQ::__config_hash{Name} = 'Multichannel Multiband EQ';

$MBEQ::__config_hash{channel_suffixes} = [qw(_L _R)];

$MBEQ::__config_hash{min_gain} = -12;
$MBEQ::__config_hash{max_gain} = 12;
$MBEQ::__config_hash{number_of_db_table_entries} = 256;

@MBEQ::bands = &{$MBEQ::__config_hash{bands_sub}}();

@MBEQ::all_band_values =
  (
  '((double)s_rate)/((double)fft_length)',
  @MBEQ::bands,
  '((double)0.5 * (double)s_rate)'
  );

@MBEQ::band_mix = ('low', @MBEQ::bands, 'high');

my @band_numbers = (0..$#MBEQ::bands);

@MBEQ::ascii_bands = map {sprintf('%02d', $_)} @band_numbers;

@MBEQ::all_ascii_bands =
('low', @MBEQ::ascii_bands, 'high');


print <<EOD
#define MALLOC_AND_CHECK(type, ptr, number_of_bytes, line_number) \\
if((ptr = (type)malloc(number_of_bytes)) == NULL) \\
  {\\
  fprintf(stderr, "$MBEQ::__config_hash{FileLabel}: !!! ERROR !!! could not allocate memory at line number %d of %s file\\n", line_number, __FILE__);\\
  exit(1);\\
  }

#define CALLOC_AND_CHECK(type, ptr, number_of_items, item_size, line_number) \\
if((ptr = (type)calloc(number_of_items, item_size)) == NULL) \\
  {\\
  fprintf(stderr, "$MBEQ::__config_hash{FileLabel}: !!! ERROR !!! could not allocate memory at line number %d of %s file\\n", line_number, __FILE__);\\
  exit(1);\\
  }

EOD
;
// PERL_END
.

The '$MBEQ::__config_hash{bands_sub} = ...' subroutine generates equalizer
target central frequencies. It should return the list of frequencies, each
frequency should be in Hz.

Please read  'DFT NUANCES AND THE EQUALIZER' section to better understand
limitations.

There are two predefined center (rather "center") frequencies: 'low' and 'high'.
The 'low' one is (Fs / N), the 'high' one is (Fs / 2).

The '$MBEQ::__config_hash{channel_suffixes} = [qw(_L _R)];' line sets
number of channels and their suffixes, i.e. per channel entities are named
according to <basename><channle_suffix> convention in the resulting "C" code.

So, for, say, 5-channel equalizer one can chnage the line to become

$MBEQ::__config_hash{channel_suffixes} = [qw(_C _FL _FR _RL _RR)];

, the suffixes meaning "Center", "Front Left", "Front Right", "Rear Left",
"Real Right". Suffixes may be whatever alphanumeric characters.

LADSPA data ports are generated in the order of the specified suffixes.

Beware of limited CPU power when thinking of increasing number of channels.


The

$MBEQ::__config_hash{min_gain} = -12;
$MBEQ::__config_hash{max_gain} = 12;

lines specify min and max band gain in db.

Db -> true gain conversion is implemented via a lookup table, so
'$MBEQ::__config_hash{number_of_db_table_entries} = 256;' line specifies
number of entries in the lookup table. See code for details, look for
NUMBER_OF_DB_TABLE_ENTRIES macro.

Overlapping/oversampling can be altered changing the value of OVERSAMP constant.
Windowing should be changed in such a case - see arcticles on DFT and Steve
Harris mbeq_1197.

The window is generated by the piece of code under
'// Create raised cosine window table' comment and is applied by the piece of
code under '// multiply input fifos by window coefficients' comment.

Frequencies between center ones have gains calculated using linear
interpolation.

Current implementation is such that first db gains are converted into true
gains, and then linear interpolation is performed using the latter.

It might make sense to first perform linear interpolation on db gains and
then to convert the interpolated gains into true ones.

The piece of Perl code generating, among other things, the interpolation
code is under/around

  if(
    db_gain_$previous_ascii_band != (*plugin_data).db_gain_$previous_ascii_band 
 || db_gain_$ascii_band != (*plugin_data).db_gain_$ascii_band
    )

lines.


6. BUILDING.
------------

This is the ugly/ad-hoc part.

There is no separate build mechanism for the released plugin, piggybacking
is used.

The sequence is this:

1) make sure FFTW3 files are installed and FFTW3 is built if there are no
prebuilt binaries and/or different optimization are desired;

2) download and unpackage swh-plugins package, I've been using
swh-plugins-0.4.11 version. Do 'cd' the swh-plugins directory.

3) ./configure swh-plugins with FFTW3 support enabled and build the plugins,
pay special attention that FFTW3 is REALLY used, watch Steve Harris' mbeq_1197
plugin;

4) if the above is successful, edit 'Makefile'. Locate in it mbeq_1197 -
related pieces and add equivalent mbeq_119700 pieces. I did this by copy-pasting
the pieces and replacing mbeq_1197 with mbeq_119700.

5) create ./.deps/mbeq_119700_la-mbeq_119700.Plo file from
./.deps/mbeq_1197_la-mbeq_1197.Plo one. Again, just copy and rename the
existing file and replace mbeq_1197 with mbeq_119700 in the new file.

If everything is OK, you have the infrastructure to build swh-plugins +
mbeq_119700.

6) put the files from this plugin tarball into swh-plugins directory.

7) run PerlPreProcessor on the Perl + "C" mixture:

\rm -r .PerlPreProcWS/ ; ./PerlPreProcessor -config_hash_file PerlPreProcessorConfigHash.c++.prl mbeq_119700.c.before_PerlPreProcessor > mbeq_119700.c

8) run 'make':

make
.

The compiled and linked plugin should be found as

./.libs/mbeq_119700.so

file.


Actually executed command lines and screen output produced by the modified
'Makefile':


if /bin/sh ./libtool --mode=compile gcc -DHAVE_CONFIG_H -I. -I. -I.    -I/usr/local/include   -g -O2 -Wall -O6 -fomit-frame-pointer -fstrength-reduce -funroll-loops -fmove-all-movables -ffast-math -fPIC -DPIC -mcpu=i686 -march=i686  -MT mbeq_119700_la-mbeq_119700.lo -MD -MP -MF ".deps/mbeq_119700_la-mbeq_119700.Tpo" -c -o mbeq_119700_la-mbeq_119700.lo `test -f 'mbeq_119700.c' || echo './'`mbeq_119700.c; \
then mv -f ".deps/mbeq_119700_la-mbeq_119700.Tpo" ".deps/mbeq_119700_la-mbeq_119700.Plo"; else rm -f ".deps/mbeq_119700_la-mbeq_119700.Tpo"; exit 1; fi
 gcc -DHAVE_CONFIG_H -I. -I. -I. -I/usr/local/include -g -O2 -Wall -O6 -fomit-frame-pointer -fstrength-reduce -funroll-loops -fmove-all-movables -ffast-math -fPIC -DPIC -mcpu=i686 -march=i686 -MT mbeq_119700_la-mbeq_119700.lo -MD -MP -MF .deps/mbeq_119700_la-mbeq_119700.Tpo -c mbeq_119700.c  -fPIC -DPIC -o .libs/mbeq_119700_la-mbeq_119700.o
`-mcpu=' is deprecated. Use `-mtune=' or '-march=' instead.
mbeq_119700.c: In function `runMChMBEq':
mbeq_119700.c:3579: warning: operation on `comp_rp_ptr_L' may be undefined
mbeq_119700.c:3580: warning: operation on `comp_ip_ptr_L' may be undefined
mbeq_119700.c:3582: warning: operation on `comp_rp_ptr_R' may be undefined
mbeq_119700.c:3583: warning: operation on `comp_ip_ptr_R' may be undefined
/bin/sh ./libtool --mode=link gcc  -g -O2 -Wall -O6 -fomit-frame-pointer -fstrength-reduce -funroll-loops -fmove-all-movables -ffast-math -fPIC -DPIC -mcpu=i686 -march=i686  -module -avoid-version -Wc,-nostartfiles  -o mbeq_119700.la -rpath /usr/local/lib/ladspa  mbeq_119700_la-mbeq_119700.lo -L/usr/lib -lfftw3f -lm   -lrt -lm -lm  -lm
rm -fr  .libs/mbeq_119700.la .libs/mbeq_119700.lai .libs/mbeq_119700.so
gcc -shared  .libs/mbeq_119700_la-mbeq_119700.o  -L/usr/lib /usr/lib/libfftw3f.so -lrt -lm  -mcpu=i686 -march=i686 -nostartfiles -Wl,-soname -Wl,mbeq_119700.so -o .libs/mbeq_119700.so
creating mbeq_119700.la
(cd .libs && rm -f mbeq_119700.la && ln -s ../mbeq_119700.la mbeq_119700.la)


7. FILE LIST.
-------------


swh-plugins-0.4.11/mbeq_119700.c.before_PerlPreProcessor
swh-plugins-0.4.11/mbeq_119700.c
swh-plugins-0.4.11/PerlPreProcessor
swh-plugins-0.4.11/PerlPreProcessorConfigHash.c++.prl
swh-plugins-0.4.11/README.multichannel_multiband_equalizer.txt - this file
swh-plugins-0.4.11/hello.c.before_PerlPreProcessor

Written by Sergei Steshenko, 02 Jan. 2006.
