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 main() { // PERL_BEGIN foreach my $name(qw(Peter Paul Mary)) { print < \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 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 < 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.