FIR-LADSPA: A LADSPA plugin for FIR filtering

flavio-PC LADSPA-FIR # chmod +x ./compile_FIR_engine.sh
flavio-PC LADSPA-FIR # ./compile_FIR_engine.sh float
compiling the FIR engine in single precision...
The FIR engine was successfully compiled into the executable file FIR_engine.exe


Perfect.
And to think that I do everything with copy and paste in order not to be wrong!
I am happy to have had two saviors on your level.
Now that I have compiled my Ferrari, I greet you and thank you.
See you when I learn to drive.
Ciao.

Grazie mille!

Drive carefully.
 
Just in case people don't know... you can use ecasound+LADSPA in combination with Pulse Audio. This You can implement both IIR and FIR filters via that route.

ALSA has a Pulse device, which IIRC appears as a virtual "monitor" device in Pulse and gets all the sounds the system sends to Pulse Audio. So, in Pulse you disable the output device you wish to use. Then you tell ecasound to use the ALSA pulse device as the input, and send the output to the soundcard you deactivated under Pulse. You can still use the Pulse audio volume control, which is very convenient, and everything works just the same as if playback was coming via Pulse except you get crossover functionality via ecasound. I use this approach on one of the systems in my home.

If someone would like, I can provide a more detailed example.
 
Last edited:
Member
Joined 2007
Paid Member
Greetings Charlie and thanks so much for this ambitious and insightful work. I am very late to this party but for happy reasons, including the trouble-free operation of ACDf. Now, in a couple days of 'down time', I am experimenting with getting LADSPA-FIR running on a AM335x SoC. In addition, I would prefer to use ALSA rather than ecasound (for multiple off-topic reasons). ALSA is not reporting any problems, but so far I'm not seeing any FFTW processes nor output derived from ALSA LADSPA-FIR plugs. This is all new to me so I have a couple very basic questions.

1. Could I see an example of text-formatted float and double precision coefficients that properly run in FFTW? I'm creating filters in Matlab, which has its own learning curve and defaults to double. Maybe my coefficient files are not correctly formatted. I first compiled FFTW for double and then secondly for float, but no idea if that was done correctly. The /dev/shm/LADSPA-FIR.log is empty and there is no engine log.
2. I believe the AM3358 has a 200MHz cycle counter, but the FFTW compilation defaults to operating with a low resolution estimate. Does your program's 'cycle latency' specification replace the function of the cycle timer?
3. Of course, the AM3358 doesn't have much horsepower so I'm also curious to learn whether ACDf and LADSPA-FIR plugs could be combined. Any thoughts? My overall goal from these experiments is to adapt crossover processing based on original file frequencies, which is easy to do with ALSA.

Very best wishes and thanks in advance for any help!

Frank
 
Member
Joined 2007
Paid Member
Some progress, but still some issues...

* I deleted and re-installed FFTW with more complete flags and it installed successfully with Neon support. Version 3.3.10.

* I found a typo in my asound.conf plug and fixed it. When sending signal to the ALSA LADSPA_FIR plugs, I now see the source in ALSA-generated hw_params. eg:
access: MMAP_INTERLEAVED
format: S32_LE
subformat: STD
channels: 8
rate: 48000 (48000/1)
period_size: 128
buffer_size: 2048

* I now have entries in /dev/shm/LADSPA-FIR.LOG. For each attempt to initiate filtering, I get 12 copies (for 6 channels) of:
The environmental variable LADSPA_FIR_FILTER_TABLE was not found.
The environmental variable LADSPA_FIR_FILTER_ENGINE was not found.

* Meanwhile, the export of these environmental variables can be confirmed using env | grep LADSPA_FIR_FILTER_TABLE, etc. However, must these assets be in a particular directory to be seen? I don't have them in the user-specific area.

Again, many thanks in advance!
 
My guess (totally a guess at this point but I think the same as phofman above) is that the environment where you have set the variables LADSPA_FIR_FILTER_TABLE and LADSPA_FIR_FILTER_ENGINE is your user environment, but you are running these via ALSA which I belive is a root process. The environmental variables created within each environment is isolated from the other. You might try some approach that sets these variables for whatever environment ALSA is running in. I am not exactly sure how to do that, or the best way to do it. phofman might be able to suggest the best course of action.
 
Well, alsa-lib is just a library, it does not start any new process. The question is what process calls the alsa routines, what is the playback app. E.g. MPD runs under a different user as a daemon.
I believe francolargo directly implements LADSA within asoundrc, but you are right that some process must open the ALSA device to play audio.
 
Member
Joined 2007
Paid Member
Thanks so much Charlie and @phofman!
I have always run this little system from the root login. ...no issues in ~6 years...
This morning I:
* put the export commands in /root/.profile, and they reliably show up with 'env' after logout-login.
* tried adding a '$' to the two variable names as part of the definition - couldn't find a quick description of the '$' role, but my export command for LADSPA_PATH uses it so, ...didn't know...
* double-checked all the chown settings - all 'root:root'
* checked the process that pipes data to the ALSA plug (SoX) and htop lists it as a 'root' process.
And I'm still getting the same result - twelve error messages in the log file.

root@beaglebone:~# env
COMP_WORDBREAKS=
"'><;|&:)
TERM=xterm-256color
SHELL=/bin/bash
SSH_CLIENT=192.168.1.134 57611 22
LADSPA_FIR_FILTER_ENGINE=:/usr/filter/LADSPA-FIR/FIR_engine.exe
SSH_TTY=/dev/pts/0
USER=root
PRU_C_DIR=/usr/share/ti/cgt-pru/include;/usr/share/ti/cgt-pru/lib
LADSPA_FIR_FILTER_TABLE=:/usr/filter/LADSPA-FIR/FIR_filter_table.txt
MAIL=/var/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/root
LANG=en_US
NODE_PATH=/usr/local/lib/node_modules
SHLVL=1
HOME=/root
LOGNAME=root
SSH_CONNECTION=192.168.1.134 57611 192.168.1.53 22
LADSPA_PATH=:/usr/local/lib/ladspa:/usr/lib/ladspa
_=/usr/bin/env
Of course! That 'smiley' must be the problem! ;)
 
Greetings Charlie and thanks so much for this ambitious and insightful work. I am very late to this party but for happy reasons, including the trouble-free operation of ACDf. Now, in a couple days of 'down time', I am experimenting with getting LADSPA-FIR running on a AM335x SoC. In addition, I would prefer to use ALSA rather than ecasound (for multiple off-topic reasons). ALSA is not reporting any problems, but so far I'm not seeing any FFTW processes nor output derived from ALSA LADSPA-FIR plugs. This is all new to me so I have a couple very basic questions.

1. Could I see an example of text-formatted float and double precision coefficients that properly run in FFTW? I'm creating filters in Matlab, which has its own learning curve and defaults to double. Maybe my coefficient files are not correctly formatted. I first compiled FFTW for double and then secondly for float, but no idea if that was done correctly. The /dev/shm/LADSPA-FIR.log is empty and there is no engine log.
2. I believe the AM3358 has a 200MHz cycle counter, but the FFTW compilation defaults to operating with a low resolution estimate. Does your program's 'cycle latency' specification replace the function of the cycle timer?
3. Of course, the AM3358 doesn't have much horsepower so I'm also curious to learn whether ACDf and LADSPA-FIR plugs could be combined. Any thoughts? My overall goal from these experiments is to adapt crossover processing based on original file frequencies, which is easy to do with ALSA.

Very best wishes and thanks in advance for any help!

Frank
I will try to answer these questions here.

1. Let's get the environmental variable stuff fixed first. But if you want to check how this is done, see lines 593-640 in FIR_convolution_engine.cpp. Text format coefficients are read one line at a time and then stof is used to convert the text to a float. I don't think double is supported for coefficients. This part of the code was thrown together and could certainly be optimized/improved but since it only runs once at startup I didn't put much effort into it and I just wanted to cover the basics. I can always modify the code if that is needed - feel free to let me know if you want to import other data types.

2. In my code, the cycle timer reports how long it takes the FIR engine to process one batch of samples and make it ready in the pipe that returns it to the LADSPA plug-in. Once you know this number you can coordinate several processes in terms of latency because you can choose it ahead of time. But you do not want to set the latency to be below the amount of time that the data is processed or there will be drop outs. I am not sure what this has to do with the "cycle counter" of your hardware.

3. Yes, you can certainly mix the IIR (ACDf) and FIR plug-ins. Keep in mind if these are operating in parallel you should consider the effect of the latency of the FIR plugin, which will cause a delay of tens to hundreds of milliseconds (depending on FIR operating parameters and available CPU horsepower) to occur for that stream compared to data running through the IIR plugin. If you operate the IIR and FIR plug-ins in series, e.g. the FIR plugin operates on the input (before splitting into channels for the crossover, which is all done via IIR) then the delay will be the same for all channels (e.g. not a problem). An application that uses a series configuration would be group delay compensation, for example.
 
Hi francolargo:

I was looking over the environmental variable dump you posted above, specifically for the two env vars that are needed to run LADSPA-FIR:
LADSPA_FIR_FILTER_ENGINE=:/usr/filter/LADSPA-FIR/FIR_engine.exe LADSPA_FIR_FILTER_TABLE=:/usr/filter/LADSPA-FIR/FIR_filter_table.txt

I suspect you might be seeing errors like The file :/usr/filter/LADSPA-FIR/FIR_engine.exe was not found.

It looks like you are using a colon or semicolon after the equals sign. These are not path variables but rather a file path. When you use the colon/semicolon that results in an invalid or not found command.

Take out the colons and check that you are using the full filepath to FIR_engine.exe and FIR_filter_table.txt, and you will be one step closer to success! ;)

P.S. you can check that the env var points to the file correctly by issuing the command:
stat $LADSPA_FIR_FILTER_ENGINE
you should see some file info (on success) or an error message on fail.

Let me know how it works out.
 
Member
Joined 2007
Paid Member
Thanks Charlie, Yeah, I was playing around with the export command because ultimately I would want the system to work untouched after booting, without ever logging in. So I put export commands both in .profile and rc.local. That colon is gone now.

I did try ecasound, which runs. The LADSPA-FIR.log remains clear. Now we have a FIR_engine.log, which reports 6 lines of "An error ocurred when trying to import system Wisdom." Apparently FFTW is not running because there is nothing else in /dev/shm. I think I will try editing all the filter coefficients down to (IIRC) 23 decimal places.
 
Ah that seems to be on me (the Wisdom error). I initially generated and used FFTW Wisom - it is supposed to maximize speed. But after some user feedback and some testing I found that it didn't really make a difference and I took out those lines in the code. Except it seems I never uploaded the updated file, so those lines are actually still in your version. You can edit them out and then run the make steps (recompile) again.

Please remove lines 662-674 in FIR_convolution_engine.cpp, shown below:
C++:
//Import FFTW system wisdom:
      #if FFT_DATATYPE == 2     
        //get FFTW System Wisdom from /etc/fftw/wisdom
        ret_val = fftw_import_system_wisdom(); //import double precision wisdom
      #else
        //get FFTW System Wisdom from /etc/fftw/wisdomf
        ret_val = fftwf_import_system_wisdom(); //import single precision wisdom
      #endif
      if ( ret_val != 1 ) {
                //ret_val is 1 on successful wisdom import
        error_file << "An error ocurred when trying to import system Wisdom." << endl;
        cleanup_and_exit( engine_stats_file, working_directory );
      }

I'm not sure why I never updated the online file with these changes. Sorry about that!

The Wisdom not found error causes an immediate termination during setup, so nothing would happen afterwards.
 
@francolargo

Do you think you will have time to try it again but with those lines (above) removed?

Someone contacted me about this a long time ago. I didn't spot it because I had created Wisdom at one point during development, and so these lines did not create an error for me. After I told them what to do I waited for them to get back to me. Some time went by and they didn't response and unfortunately I just forgot about the issue. I don't normally use this code (still plan to but not yet) so I did not run into the issue myself.

Give it a try and it should work, at least with ecasound. Then we can figure out how to get it working from within asoundrc if you run into problems implementing it that way.
 
Member
Joined 2007
Paid Member
@francolargo

Do you think you will have time to try it again but with those lines (above) removed?
I just got around to this tonight due to some travel. I'll test in the morning, first with ecasound. I must compliment your annotation!

I'll confess that I also went into LADSPA_FIR.cpp and temporarily short-circuited the two environment variables by listing the path/files directly in lines 449 and 450. [BUT - sheepishly - I have never programmed in C++ - recently only Python. I left the original code as //comment to easily restore.]
 
Member
Joined 2007
Paid Member
Here is a quick update of progress - in two stages:

Stage 1: With ecasound I believe there is a problem with input to the loop. Here's what I get:

__________________________________________________________________________
root@beaglebone:/usr/filter/LADSPA-FIR# ecasound -B:rt -z:mixmode,sum -x -f:s32_le,2,48000 -i:alsahw,1,0,0 -f:f32_le,2,48000 -o:loop,1
-a:woofer,midrange,tweeter -i:loop,1
-a:woofer -el:LADSPA_FIR,13,768,1024,5000,1 -f:s32_le,8,48000 -chorder:0,0,1,2,0,0,0,0
-a:midrange -el:LADSPA_FIR,13,768,1024,5000,2 -f:s32_le,8,48000 -chorder:0,0,0,0,0,0,1,2
-a:tweeter -el:LADSPA_FIR,13,768,1024,5000,3 -f:s32_le,8,48000 -chorder:0,0,0,0,1,2,0,0
-a:woofer,midrange,tweeter -o:alsahw,0,0

**************************************************************************
* ecasound v2.9.1 (C) 1997-2014 Kai Vehmanen and others
**************************************************************************
(eca-chainsetup-parser) Buffering mode 'rt' selected.
(eca-chainsetup-parser) Enabling 'sum' mixmode.
(eca-chainsetup-parser) Truncating outputs (overwrite-mode).
(eca-chainsetup) Chainsetup "untitled-chainsetup"
(eca-chainsetup) WARNING: Input "alsahw" is not connected to any chain. (3.1-DISCON-INPUT)
(eca-chainsetup) WARNING: Output "loop" is not connected to any chain. (3.2-DISCON-OUTPUT)
(eca-chainsetup) Multitrack-mode enabled.
(eca-chainsetup) "rt" buffering mode selected.
(eca-chainsetup) Opened input "alsahw", mode "read". Format: s32_le, channels 2, srate 48000, interleaved.
(eca-chainsetup) Opened input "loop", mode "read". Format: f32_le, channels 2, srate 48000, interleaved (locked params).
(eca-chainsetup) Opened input "loop", mode "read". Format: f32_le, channels 2, srate 48000, interleaved (locked params).
(audioio_alsa) Warning! Period-size differs from current client buffersize.
(eca-chainsetup) Opened output "alsahw", mode "write". Format: s32_le, channels 8, srate 48000, interleaved.
  • [ Connected chainsetup: "untitled-chainsetup" ] ------------------------
  • [ Controller/Starting batch processing ] -------------------------------
  • [ Engine - Driver start ] ----------------------------------------------

^C

  • [ Controller/Batch processing finished (0) ] ---------------------------
  • [ Engine exiting ] -----------------------------------------------------
(eca-control-objects) Disconnecting chainsetup: "untitled-chainsetup".
* Error in `ecasound': double free or corruption (!prev): 0x005f5090 *
Aborted
__________________________________________________________________________________

I find the ecasound syntax for ALSA input to be confusing and the only input syntax that gets anything close to working is '-i:alsahw,1,0,0'. None of the other syntaxes such as '-i[:]alsa,pcm_device_name', or 'i[:]alsaplugin,card_number,device_number,subdevice_number' got me any closer to filtering. HTOP would show one or more ecasound processes but they weren't using any CPU.

Stage 2: LADSPA plugins in asound.conf - In programs other than ecasound I can easily direct PCM to individual ALSA plugs. I made a plug that substituted LADSPA_FIR for ACDf, and it seems to be doing its job. One thing I noticed is that I can't include the variable 'filter_label', or the log file reports problems creating the dev/shm directory. No worries, leave that off of the line (e.g. 'input { controls [13 768 1024 5000] }'), which definitely causes a ruckus on HTOP! :) There are a few hundred attempts to create /dev/shm/ content. When that dies down the LADSPA-FIR log contains line after line of 'When trying to open the filter table file, an error occurred or the file could not be opened.' Ownership on everything is root:root. Here is the top of the trial filter file that I am using, having the filename: 'high-flattop5k-256-48.txt'.
_________________________________________________________________________________

# Numerator:
0.0000009067867689636690
0.0000010847667953157917
0.0000008631128514080137
0.0000001866277132915710
-0.0000008629598068801169
-0.0000019532685625931040
-0.0000025123060125038014
-0.0000019440182962596641
0.0000000000000000001662
0.0000028793356785954727
0.0000054994091167238521
0.0000062841529324076090
0.0000040337565737242785
-0.0000012404160292016499
___________________________________
255 coefficients, all truncated to 22 decimal places.

I suspect that we're close! Could I see the top of a working coefficient file? I definitely need to get on the Matlab learning curve and have it correctly format coefficients, but I don't know what would be better - text or binary.

Also, here is the content of the filter table:
____________________________________
#ID TAPS FILTER_FILE <-- header lines beginning with "#" are skipped
1 256 /usr/filter/LADSPA-FIR/filters/lowkaiser9-100-256-44.1.txt
2 256 /usr/filter/LADSPA-FIR/filters/mid-flattop5k256-44.1.txt
3 256 /usr/filter/LADSPA-FIR/filters/high-flattop5k256-44.1.txt
11 256 /usr/filter/LADSPA-FIR/filters/lowkaiser9-100-256-48.txt
12 256 /usr/filter/LADSPA-FIR/filters/mid-flattop5k-256-48.txt
13 256 /usr/filter/LADSPA-FIR/filters/high-flattop5k-256-48.txt
____________________________________

and here are the two lines I modified in LADSPA_FIR.cpp

FIR_filter_table_filename = "/usr/filter/LADSPA-FIR/FIR_filter_table.txt"; // was GetEnv("LADSPA_FIR_FILTER_TABLE");
FIR_engine_cmdstr = "/usr/filter/LADSPA-FIR/FIR_engine.exe"; // was GetEnv("LADSPA_FIR_FILTER_ENGINE");


Cheers,

Frank
 
Last edited:
It might be a good idea to stick to 2 channel and ecasound for the time being. These are the conditions I tested the code under.

The first thing I see is that your ecasound commands don't have a backslash at the end of each line except the last, which is required for line continuation. Not sure if you left those out on purpose in your posted ecasound commands?

As far as the format goes for text file input, I don't have the original files that I used during development but I got them from rePhase. I simply plugged in a prefab filter type and tried a few filters with up to 64k coefficients in both text and binary (32bit) formats. As an example I have attached a text format LR2@1kHz filter with 8192 taps that I just generated in rePhase.

Also, all inputs to LADSPA must of type float or can be converted internally to a float (e.g. an integer). So about the only option I could implement for a filter_label is an integer, which at least gives the user a veyr basic way to discriminate between directories in the tempfs if only one of their filters is not working properly (for example). Its not at all required for proper execution. FYI here is the relevant code from LADSPA_FIR.cpp:
C++:
case 4:
      //filter label, whole number part is used as the dir name for the filter, passed as float
      temp = *data;
      temp = abs( trunc( temp ) ); //truncate to whole number, make sure is >= 0     
      FIR->filter_label = static_cast<unsigned int>(temp); //cast to u_int;
break;
and then to create the actual dir in /dev/shm from that:
C++:
//create unique working directory in the tmpfs
    if ( FIR->filter_label == 0 ) {
      dir_name = "/dev/shm/FIR_" + get_random_char_str( 6 );
    } else {
      temp_string = to_string( FIR->filter_label );
      //pad the front of the string with zeroes until the string is four characters long
      while ( temp_string.size() < 4 ) {
        temp_string.insert(0,"0");
      }
      dir_name = "/dev/shm/FIR_" + temp_string + "." + get_random_char_str( 2 );
    }
    OS_commands = "mkdir " + dir_name;
    ret_val = run_OS_command( OS_commands );
    if ( ret_val != 0 ) {
      //an error was thrown when trying to make the unique directory. Set fatal error flag
      FIR->fatal_errors_encountered = true;
      error_file << "An error was thrown when trying to make the unique directory " << dir_name << endl;
      return;
    }
    strcpy( FIR->working_directory, dir_name.c_str() );  //store for later

So integer labels not starting with a zero will work such as 1, 10, 101, 102, 103... etc. Give that a try. I think I originally envisioned the filter table filters to use the same numbering scheme so that it would be easy to figure out which was which.

You wrote:
When that dies down the LADSPA-FIR log contains line after line of 'When trying to open the filter table file, an error occurred or the file could not be opened.' Ownership on everything is root:root.

The program calling alsa must be a root-owned process for this to work. As phofman pointed out, some programs operate under their own user accounts. Root ownership is actually rather restrictive and it would be better to use something like this:
Bash:
chmod 666 FIR_filter_table.txt #allow read and write access by any account, deny execution for all
chmod 777 LADSPA-FIR/FIR_engine.exe #allow read write and execute by any account
 

Attachments

  • LR2-1k.txt
    260.6 KB · Views: 56