Spectral Bandpass Filter Plugin

Hi all,

Here is a plugin which I hope will be useful to some.
My first foray into Lisp territory, with gracious help from Steve.

It applies a bandpass filter effect to a selection in spectral view/edit.
Essentially, it’s the opposite of SpectralEditMulti.ny (by Paul Licameli and updated by Steve Daulton) which notches out the selection.

The passband (Q) is computed and applied automatically.
The rolloff is not that steep, so if you need more isolating/rejection of unwanted frequencies, run it several times.

An example after a single run:
Screen Shot 2021-05-09 at 8.24.27 PM.png
The plugin is below, install it like any other Nyquist Plugin.
Don’t forget to enable it before trying to use it in Audacity.
Please note that I have only tried it in Audacity Ver 2.3.1
SpectralEditBPF.ny (739 Bytes)

Looks good here :slight_smile:

One comment:
Normally the plug-in “headers” should be written as normal comments with a single semicolon as the first character.

$nyquist plug-in
$version 4
$type process spectral
$name (_ "Spectral edit BPF")
$action (_ "Filtering...")
$author (_ "Paul")
$release 0.1.0
$copyright (_ "Released under terms of the GNU General Public License version 2")

would normally be written as:

;nyquist plug-in
;version 4
;type process spectral
;name "Spectral edit BPF"
;action "Filtering..."
;author "Paul"
;release 0.1.0
;copyright "Released under terms of the GNU General Public License version 2"

The reason that shipped plug-ins use the “$” symbol is so that Audacity can intercept the “Underscore” macro and replace the enclosed string with a translated string from gettext. This is a complication that does not normally concern 3rd party plug-ins because Audacity only has the translated strings for plug-ins that are shipped as part of the Audacity release.

By using the syntax:

$keyword (_ "quoted string")

you are telling Audacity to look up “quoted string” in it’s translations, which will always silently fail, except for shipped plug-ins that have been translated into other languages.

Thanks Steve.

Have updated the original download link with the newer version.

Also bumped up the ver from 0.1.0 to 0.1.1 to avoid any confusion due to the incorrect header.

Hope this very simple plugin will motivate others to add or improve on it and also write their own.
Nyquist plugins add a lot of power and versatility to Audacity.
It’s definitely worth investing some time and effort into learning it.

It works fine for me with Audacity 3.0.2.

There is the possibility of the plug-in failing (user error). For example, if there is no spectral selection, or if bandwidth is nil.
You could include a check to ensure there is a valid selection using an IF statement.
In pseudo code:

IF octaves == NIL
THEN
"Error"
ELSE
Apply-filter

In Nyquist code:

(if (not octaves)
    "Error.\nA spectral selection is required."
    (progn
      (setf q (/ (sqrt (power 2.0 octaves))(- (power 2.0 octaves) 1)))
      (bandpass2 *track* center q)))

PROGN creates a block of code and returns the result of the final expression (the filtered audio).

IF: XLISP if
PROGN: XLISP progn

Thanks Steve, very helpful info there.
Yep, I didn’t put in any error checking code, bad !!
Will modify it shortly, test and repost.

By the way, to get more familiar with the syntax and all the brackets, leaving aside what the pgm is supposed to do for now,
is the following correct?

; EXAMPLE 1
; ----------

(if (not octaves)
    (setf q 0.001)  ; condition 1 if true
    (setf q 20))    ; condition 2 if false and has 2 closing brackets to indicate end of setf and if
    (bandpass2 *track* center q)   ; carries on
    

; EXAMPLE 2
; ---------
    
(if (not octaves)
    (progn (setf SomeVar 0.001)   
               (setf AnotherVar 20))   ; has 2 closing brackets to indicate end of setf and end of progn
    (progn (setf SomeVar 0.275)
               (setf AnotherVar 40)))  ; has 3 closing brackets to indicate end of setf, progn and if
    (bandpass2 *track* center q)   ; carries on
(if (not octaves)
    (setf q 0.001)  ; condition 1 if true
    (setf q 20))    ; condition 2 if false and has 2 closing brackets to indicate end of setf and if
    (bandpass2 *track* center q)   ; carries on

This is easier to read like this:

(if (not octaves)
    (setf q 0.001)  ; condition 1 if true
    (setf q 20))    ; condition 2 if false and has 2 closing brackets to indicate end of setf and if
(bandpass2 *track* center q)   ; carries on

Yes, that will work as described, provided that “center” also has a valid value, BUT watch out for:

(setf q (/ (sqrt (power 2.0 octaves))(- (power 2.0 octaves) 1)))

If “octaves” is NIL or zero then the code will fail.



(if (not octaves)
    (progn (setf SomeVar 0.001)   
               (setf AnotherVar 20))   ; has 2 closing brackets to indicate end of setf and end of progn
    (progn (setf SomeVar 0.275)
               (setf AnotherVar 40)))  ; has 3 closing brackets to indicate end of setf, progn and if
    (bandpass2 *track* center q)   ; carries on

Yes, that should work, but better to write that as:

(if (not octaves)
    (progn (setf SomeVar 0.001)   
           (setf AnotherVar 20))   ; has 2 closing brackets to indicate end of setf and end of progn
    (progn (setf SomeVar 0.275)
           (setf AnotherVar 40)))  ; has 3 closing brackets to indicate end of setf, progn and if
(bandpass2 *track* center q)   ; carries on

so that we can then see at a glance that:

  1. the first “progn” block is done if the condition is true,
  2. else the second “progn” block runs,
  3. and the final line is outside of the conditional statement.


    Another useful way to handle blocks of code conditionally is with COND (See: https://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-079.htm). This has the additional advantage that it can define multiple test conditions (if something, then something, else if something else, …)


    By the way, Top Tip: If your text editor does not have “parentheses matching”, get one that does. (Sorry, I don’t have any recommendations for text editors on macOS. For Windows I’d suggest NotePad++.)

Thanks for the confirmation that my syntax is correct, starting to get the hang of it.
Yes, the indentation came out a bit weird even when using the CODE tags with [ and ].

In my text editor it looks likes the screenshot below, as you say, much easier to read.
BTW, for Mac there are two choices (that I know of) for good text editors:

  • Sublime Text (not free but has a non expiring demo period and they rely on users to do the right thing).
  • Editra.

The second is what I use, but unfortunately it’s no longer being developed.
Not quite sure where to download an old version (I use 0.7.20), there seems to be plenty of dodgy sites that claim you can download it from them.
I have the dmg, but not sure how legal it is to share it, even though it was freeware.

What I like about Editra is, matching brackets turn blue when you click on one of them and red when there is no other matching one.
Example shown below where I purposely inserted an extra bracket on the last line.
E1.png

Perhaps you used tabs instead of spaces. Always use spaces.


There’s also Atom, which I believe is free and widely used (I don’t use it myself as we are spoilt for choice on Linux). Apparently Atom can be a bit slow with big projects, but that’s unlikely to be a problem with Nyquist plug-ins that are usually less than 200 lines.


Yes, that’s incredibly helpful for LISP languages.

Perhaps you used tabs instead of spaces. Always use spaces.

Aha, mystery solved, I did use tabs.

It’s getting pretty late here (going for 1am), so will add error checking to the plugin after a good night’s sleep and upload later in the morning.

There’s also Atom

I downloaded it and had a look inside the app structure before installing or running it.

On closer inspection found out why it runs slow on big projects.
The amount of frameworks it uses plus json, javascript, cocoa, node and other resources and libs is just mind boggling.
No wonder the download is 210MB, whilst the actual main binary executable is only 300KB.
I’m sure for other tasks it’s fine but like you say, for small Lisp programs it’s a bit of an overkill.
Thanks anyways for mentioning it.

BTW, sorry if I’m getting side tracked but maybe this extra info will benefit other readers who are thinking of starting with Lisp as well.

With regards to that site you always refer to, there is a zipped archive of the Nyquist ref manual.
https://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/manual/download/nyquist-2.37-html.zip
Nice to have it available locally.
I’m assuming that it’s not 100% compatible with Nyquist Lisp as I see commands like SUM are not listed and others that are listed
like direct file operations would not work in Audacity.

There is also a PEEK and POKE command, do those work and I’m assuming that the Lisp built into Audacity is sandboxed so as to prevent accidental (or otherwise) poking at random RAM and I/O locations.

The latest Nyquist manual is also available on-line here: Nyquist Reference Manual
I frequently refer to the Nyquist language reference: Index
and the XLISP reference (Nyquist is an extension of XLISP): XLisp
The XLISP provides examples for nearly all of the built-in XLISP commands.

Audacity 3.0.2 is pretty up to date with stand alone Nyquist at the moment as Audacity had a recent update of the Nyquist library.

Yes, some functions and features are missing from the Audacity version, such as PEEK and POKE, and also some XLISP commands such as SYSTEM.

Nyquist is not actually sandboxed (you can still crash Audacity if you try), but most of the dangerous functions have been removed.


Nyquist can however read and write files. For example, take a look at Sample Data Import, and Sample Data Export. (The code for these plug-ins can be found here: https://github.com/audacity/audacity/tree/master/plug-ins). Whatever file permissions are normal for your user account apply when reading / writing with Nyquist.

Other useful documentation can be found in the Audacity wiki: https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
Here you will find Nyquist features that are unique to Audacity.

Thanks for the links, very useful.

Right, I have made the recommended changes to include some error trapping/messaging.
I didn’t include one for “center” as it will never have a nil.
It may be a wrong center frequency (Audacity and Nyquist can’t guess what the right one should be), but never a nil.

Screenshot below.
If you are happy, will update the download link.

Thanks,
Paul
E2.png

That’s not actually true, but it is true that “center-hz” will never be nil unless “bandwidth” is also nil.

Here’s an example of where both “center-hz” and “bandwidth” are both NIL:

Very happy :slight_smile: though perhaps you could mention in the comments that the frequency range of the selection sets the (-3dB) bandwidth of the filter, or something like that.

Nice that it has error checking. Many of the early Nyquist plug-ins had no error checking and would just fail silently if used inappropriately, or with unhandled settings.

Good point Steve.

Below, some error checking for “center” as well.

I’m trying to understand the syntax well.
Hence, decided to use nested “if’s” instead of “Cond”.
There is a problem though, the “Error” print out line also terminates the script,
which I assume is different to a “print” command that will print out the message then continue.

Over and above, there is the pesky detail about using a closing bracket for the first “If”.

So the approach taken, I set a dummy variable which then gives me the opportunity to properly end the first “if”.
Could probably have also used just a (progn))
Hope that makes sense.

No doubt there are more efficient ways to code this (ahem “Cond”), but for now just need to wrap my head around doing it this way.

Your thoughts?

(setf center (get '*selection* 'center-hz))
(setf octaves (get '*selection* 'bandwidth))
(if (not octaves)
    "Error.\nA spectral selection is required."
    (setf dummy 1))
(if (not center)
    "Error.\nNo center frequency selected."
    (progn
      (setf q (/ (sqrt (power 2.0 octaves))(- (power 2.0 octaves) 1)))
      (bandpass2 *track* center q)))

EDIT:

Thinking about it a bit more, would doing the following be OK?

(bandpass2 *track* center q))))

Extra “)” to properly terminate the first “if” and thus, negating the need for setf dummy ?

An important feature of Nyquist plug-ins / scripts is that they return ONE result to Audacity. This is called “the return value”.
They can’t display a message and then continue.
If the return value is text (or a number), then Audacity will display that in a message window.
If the return value is a sound, then Audacity will replace the selected audio with the sound.
For full details, see: Missing features - Audacity Support


Error checking for both center and bandwidth is superfluous. If center is NIL, then bandwidth will also be NIL, and vice versa.

(if (not octaves)
    "Error.\nA spectral selection is required."
    (setf dummy 1))
(if (not center)
    "Error.\nNo center frequency selected."
    (progn
      (setf q (/ (sqrt (power 2.0 octaves))(- (power 2.0 octaves) 1)))
      (bandpass2 *track* center q)))

If “octaves” is nil, then the first IF clause returns the string, “Error.\nA spectral selection is required.”. We’re not doing anything with that string (we’re not printing it) and there is no unhandled error, so the script continues to the next IF clause.

If “center” is NIL (which it will be when “octaves” is nil), then the second IF clause returns “Error.\nNo center frequency selected.”.
The PROGN block is not reached when “center” is nil, so the script has completed and the final return value (“Error.\nNo center frequency selected.”) is returned to Audacity.
Because the return value from the entire script is a string value, Audacity displays it in a message box.

You can just leave out the first IF clause because it will never do anything.


About PRINT:
The PRINT function prints to the debug stream and returns the string.

Consider this code:

(setf my-val (print "Hello World"))
(print "Goodbye")
my-val

Now run the code in the Nyquist Prompt and use the Debug button.

The return value from the script is “my-val”, which evaluates to “Hello World”, so Audacity displays “Hello World”.
Because we used the Debug button, Audacity will then open the Debug window, and we will see:

"Hello World"
"Goodbye"

“Hello World” was printed to the debug window by the PRINT statement in the first line of the script.
“Goodbye” was printed to the debug window by the PRINT statement in the second line of the script.

If you run the code with the “OK” button rather than the “Debug” button, then you only see the script’s return value and not the Debug window.

Hopefully this will make sense when you try it :wink:

OK, I think I got it.

So, Nyquist will only “return” a single thing (text, values, sound) and terminate, essentially then a print also contains an end or terminate, so to speak.

Because the return value from the entire script is a string value, Audacity displays it in a message box.

Got it.

So if the two variables were completely independent, then a “print” (or just “Some Warning Text”) if any one was a nil, would stop the script.

I was under the impression that Nyquist was a “dynamic” thing that ran in the background and Audacity was just the GUI for it and it could interact (i.e. output multiple things) and Audacity would display them either as a message or prompt for user input.
By prompt, I mean a prompt after the initial start of execution, for example:

A user selects a certain filter, the script prompts for certain values.
(I know that sliders can set limits but let’s overlook that for now).

The script starts and for some reason, one of the values don’t make sense, the script could then re-prompt the user with two choices:

  • Correct the value, or
  • Terminate.

Understand now that Audacity waits for the first output from Nyquist and at that point it takes over again and the script stops.
At this point, all script data in RAM is cleared except for “scratch”.
If the above is true, then is it a fair assumption that Nyquist does all the garbage collecting and no need for the user/script to invoke it?

Wow, this is more fun than I expected…seriously, I know, I’m a nerd, what can I say.

Will update the script accordingly.

Yes, Nyquist will only return one single thing. (a stereo sound is ONE array containing two sounds).

PRINT returns a string value, and also has a “side effect”. The side effect is that it prints to the debug stream.

If you run this in the Nyquist Prompt:

(print "Hello")

you will see that it returns “Hello”, which Audacity displays in a message window.

If you run this in the Nyquist Prompt:

(print "Hello")
(print "World")

You will see that the script returns the final value: “World”,
but if you use the Debug button, you will see both side effects:

"Hello"
"World"

(This can be extremely useful when debugging a script).

Not yet, though that “may” come in the future.

Nyquist is really a separate application (a computer language), that was shoehorned into Audacity way back in the early history of Audacity’s development.

The basic model is:
Audacity passes information to Nyquist.
Nyquist runs, then passes something back to Audacity.
Audacity tries to do something sensible with what it gets back from Nyquist - if it can’t, then it throws an error.

An example of Nyquist returning something that Audacity can’t handle:

(list 1  2)  ;a list with two integer elements
;; Audacity does not know what to do with this, so 
;; pops up an error message: "Nyquist returned a list."

(This can be extremely useful when debugging a script).

Absolutely.
Strategically placed print commands in the script will be very helpful.

You have helped me tremendously, thank you.
I’m sure it will be equally as helpful to others wanting to start with Nyquist.

Nyquist is really a separate application (a computer language), that was shoehorned into Audacity way back in the early history of Audacity’s development.

And thank you to the person/people that made that decision.
It gives Audacity so much more versatility.

I am enjoying Lisp but, still of the opinion that if a “simpler” language was included, or some kind of converter/parser from say Basic or C to Lisp/Xlisp, it would have a much greater take-up.
Even if that converter/parser was external to Audacity, it would be great.
Open the converter, write the code in Basic or C, it generates the Lisp code, copy and paste into the Nyquist prompt.
Not that much of a hassle.

I did look at SAL, but since Lisp seems to be more common, there are many more examples and references for Lisp.

In most cases you don’t need to worry about garbage collection. Nyquist handles this itself.

There are some cases where garbage collection are a concern, but that’s probably best left 'till later as it gets quite complex and technical. The classic case is that when trying to process very long tracks, you have to be careful to allow Nyquist to garbage collect while processing the track - many of the early Nyquist plug-ins failed to do this, and are unable to process very long tracks.

That was Dominic Mazzoni and Professor Roger Dannenberg - Audacity’s founders.
I’m also really glad that they did :ugeek: