Shelf Filter

Split from https://forum.audacityteam.org/t/reducing-bass/7926/1

This is a revised version of the shelf filter from that topic.
It has improved error checking but should otherwise be the same.

Unless anyone can spot any bugs or typos I think this is ready to be uploaded to the wiki.
shelf.ny (2.45 KB)
Download shelf.ny

Latest version is available on the wiki: http://wiki.audacityteam.org/wiki/Nyquist_Effect_Plug-ins#Shelf_Filter

Setting filter gain below about -12 with low shelf and “Nyquist did not return audio”.

I found the “mid band” setting confusing. I was expecting a simultaneous application of low and high shelf but instead I got a band pass/reject between the two frequencies.

– Bill

Confirmed, the same with me.

It’s a bug in Nyquist, if I write this in the Nyquist prompt:

(eq-lowshelf s 800 -15)

then I get exactly the same error: “a0 < 1 in biquad”. There’s either something wrong in the eq-lowshelf math, transforming the lowshelf parameters into biquad parameters, or in the Nyquist biquad functions themselves.

  • edgar

This is due to the “fix” for bug 152 Log in to Bugzilla
(I did mention at the time that “This is not a fix, just a workaround”)
I’ve reopened the bug.

I’ve proposed a new patch for bug 152 that doesn’t break eq-lowshelf.

Does anyone have in depth knowledge of biquad filters? (Edgar-rft?)

If anyone has time to test the patch, all you need to do is find the file dspprims.lsp (in the Audacity Nyquist folder) and replace it with the attached version.
dspprims.lsp (17.7 KB)
This is the diff if you want to see what has changed:

Index: nyquist/dspprims.lsp
===================================================================
--- nyquist/dspprims.lsp	(revision 11739)
+++ nyquist/dspprims.lsp	(working copy)
@@ -342,11 +342,9 @@
 
 ; convenient biquad: normalize a0, and use zero initial conditions.
 (defun nyq:biquad (x b0 b1 b2 a0 a1 a2)
-  (if (< a0 1.0) 
-    (error (format T "a0 < 1 in biquad~%"))
-    (let ((a0r (/ 1.0 a0)))
-      (snd-biquad x (* a0r b0) (* a0r b1) (* a0r b2) 
-                               (* a0r a1) (* a0r a2) 0 0))))
+  (let ((a0r (/ 1.0 a0)))
+    (snd-biquad x (* a0r b0) (* a0r b1) (* a0r b2) 
+                             (* a0r a1) (* a0r a2) 0 0)))
 
 
 (defun biquad (x b0 b1 b2 a0 a1 a2)
@@ -366,6 +364,8 @@
 
 ;; NYQ:LOWPASS2 -- operates on single channel
 (defun nyq:lowpass2 (x hz q)
+  (if (or (> hz (/ (snd-srate x) 2.0))(< hz 0))
+    (error "frequency out of range" hz))
   (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
          (cw (cos w))
          (sw (sin w))
@@ -383,6 +383,8 @@
   (multichan-expand #'nyq:highpass2 x hz q))
 
 (defun nyq:highpass2 (x hz q)
+  (if (or (> hz (/ (snd-srate x) 2.0))(< hz 0))
+    (error "frequency out of range" hz))
   (let* ((w (* 2.0 Pi (/ hz (snd-srate x))))
          (cw (cos w))
          (sw (sin w))

That seems to have fixed it.

I still think the description of the mid-band behaviour could be better, but I don’t know how without getting too wordy. Will there be a README zipped with the effect?

– Bill

It is a simultaneous application of low and high shelf, but the filter slope is not very steep, so if the low and high cut-off frequencies are fairly close together then it does not look so obvious.

Here’s a spectrum plot with a fairly wide mid-band (200 Hz to 8 kHz, +6 dB mid-band gain)
mid-band-boost.png

One if the differences between an rfTechnician and an Engineer is that the technician doen’t need to know anything about biquad math :confused:

The snd-biquad function is implemented in “audacity/lib-src/libnyquist/nyquist/tran/biquadfilt.c”, generated from “biquadfilt.alg” in the same directory.

I will try to find out what the reason is. I assume that the “new” dspprims.lsp is the old one with the argument tests removed, so we will now probably have the original bug 152 again, but I still have not tested yet.

  • edgar

Correction: I just realize that you have shifted the argument tests from the biquad function to the highpass2 and lowpass2 functions, so my assumtion from above is probably wrong, but nontheless I will try to test it in depth later on…

It would be possible to add a “slope” parameter though the defaults usually work well for the purpose of “cutting/boosting bass” or “cutting/boosting treble” so I’m not sure that it is worth the extra complication. What do you think?
shelf.ny (2.57 KB)

I wasn’t planning to. If it is too complicated I could disable the mid-band option

It just needs ,mid-band deleting from the line:

;control filter-type "Filter type" choice "low-shelf,high-shelf,mid-band" 0

so that it is:

;control filter-type "Filter type" choice "low-shelf,high-shelf" 0

Yes, but with +6 dB gain I was expecting a 6 dB boost below 200 Hz and above 8 kHz, based on the description.

– Bill

The description says “mid-band”, so I would expect that “boosting” (positive gain) with that filter will “boost the mid-band”. If it boosted everything outside of the mid-band then for myself that would seem back-to-front. Either way the user will discover which way round it is on first use.
Ideally I’d rather do away with the wording about which sliders are used and which not and just grey-out the unused sliders, but Nyquist plug-ins can’t do that.

The “filter frequency” for shelf filters are invariably specified as the half gain frequency and that is the case here, so the term “cut-off” frequency is probably misleading.

Can you suggest better terms for the controls?
Can you suggest better (brief) “info” wording?

Yes, the user will figure it out (I did).

Titles of the controls are fine. The distinction between “cut-off” and “half-gain” point is not worth worrying about IMO.

“The mid-band filter uses the cut-off frequencies to specify the upper and lower frequencies of the band.”

– Bill

How about something like this?
shelf-filters.png

I get lost in the C++ code, but my best guess from tracing the problem back from:
“highpass2 → nyq:highpass2 → nyq:biquad-m → nyq:biquad → snd-biquad → ??(I lose it here)?? → biquadfilt.c”
the problem appears to be that in certain cases the iterations (inner loop) in biquadfilt.c can make the sample values increase like a feedback loop to infinity and beyond.
I’d guess that the “proper fix” would be to put a check into biquadfilt.c to limit the sample values to less than infinity.

I don’t think that an error message for nonsense values in nyq:highpass2 is a bad idea, assuming that I got that right.

I like it, but it may be getting too complex already. I especially like the slope option.

– Bill

Perhaps the minimalistic approach?
shelf-minimal.png

I prefer the gain control at the bottom.
I’ve also moved the default low-shelf frequency down to 400 Hz.
shelf.ny (2.42 KB)

You’re closer than you might think: (snd-biquad sound b0 b1 b2 a1 a2 z1init z2init) is nothing but a direct Lisp wrapper around:
snd_biquadfilt(sound_type s, double b0, double b1, double b2, double a1, double a2, double z1init, double z2init) in “biquadfilt.c”

This direct calling of C code is the reason why the SND-… functions crash with wrong-typed Lisp argument values. A further complication is that the C code in “biquadfilt.h” and “biquadfilt.c” is not written by a human, instead it is auto-generated by the intgen macro-processor (see “intgen” Appendix in the Nyquist manual) from the definitions in “biquadfilt.alg”, so beside C you also need to understand intgen, what I only partially do.

Yes, “negative infinity overflow” was the first thing that came to my mind when I read “returns only -1 samples” in the original description of bug 152.

This is not an easy thing because IEEE Inf and NaN are treatended inconsistently with different C compilers on different operating systems.

The problem is that the mathematically correct behaviour of floating-point numbers would be:

  • number > most-positive-float => error: positive floating-point overflow
  • number < most-negative-float => error: negative floating-point overflow
  • 1.0 / 0.0 => error: division by zero

But IEEE floating-point numbers are designed to be used in physics simulations, so the values Inf (infinity) and NaN (not-a-number) were introduced to avoid floating-point errors, what has to the consequence that IEEE floats have a very weird behaviour with very big or very small numbers:

  • number > most-positive-float => +Inf
  • number < most-negative-float => -Inf
  • 1.0 / 0.0 => +Inf
  • 1.0 / Inf => 0
  • Inf / Inf => NaN
  • 1.0 / NaN => NaN

You remember the “1 / 0Hz = infinite time” discussion? Mathematical division is only one of many cases where Inf and NaN must be treatened differently than “real” floating-point numbers, that are not at all clearly defined in the IEEE standard.

See David Goldberg What Every Computer Scientist Should Know About Floating-Point Arithmetic for another myriad of details.

The problem is that in the real world the behaviour of IEEE floats is mathematically incorrect with values that do not fit into the specific floating-point format (32-bit, 64-bit, etc.), and to write code that catches these errors is many times slower than just simply accepting these errors because IEEE floats are implemented in hardware, not in software. The only other way is not to use IEEE floats at all (see e.g. http://gmplib.org/), but with the inevitable disadvantage that using software floats will make Nyquist many times slower.

I will ask some software engineers here (who know this better than me) what to do in such a case. Every C standard library provides IEEE floating-point “traps” to catch such errors, but this will probably end with platform-dependent #ifdef code.

This is never a bad idea because Nyquist lacks a lot of argument checks for wrong values, what has to the consequence that you get huge error backtraces where you first have to travel back several miles until you find the real error.

IMO every Nyquist high-level function (the functions that are intended to be called directly from user code) should have tests at least for obviously wrong argument values. The problem here is that XLISP and Nyquist are designed as experimental languages where doing experiments always includes doing wrong things (and hopefully learn something out of it), so Roger deliberately omitted these tests. The other reason is that argument tests always introduce restrictions, so finding good argument tests without introducing unnecessary restrictions is often more difficult than designing the language itself.

Once again a megaton of details to consider for a problem that initally looked soooo simple … :cry:

  • edgar

which was the problem with my original bug 152 patch - it unnecessarily restricts valid arguments for the shelf filters.

So would that be using Trap Handlers ?