Notch filter with loop

Hi everyone,
So I’m faced with a rather daunting task here to correct a tonal issue of one of my favorite instruments.
I have severl solo recordings of Italian Zampogna bagpipes, and most of them, regardless of their key, have this absolutely piercing, loud “sopranina” drone that gets totally in the way of anything else, which makes it absolutely impossible to hear anything besides it, even though this isn’t intentional. Obviously the frequencies will change depending on the sopranina’s note, but there are times when I only know the base frequency to notch and would want to notch out all the other harmonics of that frequency with the same Q value.
I always did it manually by simply calculating the frequency of each and every harmonic of the sopranina, but this is madly time-consuming for a blind person like myself, and there is always the potential error of being wrong when typing in each frequency. So I’d like to see if anyone can come up with this.
Basically it would be a plugin (not just a Nyquist prompt), a Notch filter that would notch out first the base frequency that you type in, then all the other harmonics (it would add your frequency to the number in a loop), and the Q value for all the frequencies would be stored. For example, I have a nice track where a bass Zampogna is being played along with a (not-so-good) singer, and after matching the pitch of the sopranina to a Sawtooth wave, I determined that the sopranina’s frequency is 305. I did try this manually, but boy was it painfully tedious!
I think I might know enough about Nyquist to convert the Notch function into the EQ function, but I thought I could start by this Notch plugin, in conjunction with mixing it with the original to even out the sopranina with the other pipes, to make remastering the Zampogna recordings a breeze. I simply could do an inverted delay, but for one, it’s not too accurate, 2, you have to divide 1 by the frequency you’re trying to counteract (not so easy), and 3, the odd harmonics an octave below are greatly emphasized, not what I want here.
Again, it’s simply a Notch filter function just with an added loop counter. It would do this for every subsequent harmonic up to the sample rate. The Hum Remover plugin doesn’t accomplish my task so I thought I’d see about this kind here.
I’ve attached the beginning of that nice solo with a rather overbearing sopranina so you can hear for yourself what I’m refering to. I’ve deleted the first verse onwards, so it’s just a solo and a quick solo at that. I’m absolutely sure you’ll hear why I hate that sopranina!

Thanks


Michael

It’s similar to the vuvuzela problem. DeHum plugin does remove most of it if set to 152.5Hz …

The slight vibrato means the notch-filters won’t remove it all.

Nice try, Trebor. However, I noticed that you removed both the tenor AND sopranina drones. My intention was only to delete just the sopranina, just harmonics of 305, and keep the tenor’s odd harmonics. I’d mix with the original and adjust the volume of sopranina so the pipes are rich, yet not totally drowned out by the sopranina as originally heard. In the modern world, nobody uses sopranina drones anymore. This was recorded in the 70’s, and in fact I did not notice any vibrato myself until you pointed that out! Go figure, I have perfect pitch myself!
Well, I have played around with the DeHum remover and it looks promising, but the problem with that is that I notice the Q gets tighter the higher you go, but in the code there is no indication of that. So I tried to design one that basically literally does notch filters, including Q values, in a loop. I’ve tried reading the debug output several times and I’m just sunk. Thing is, nobody taught me to program in Nyquist, so do you mind fixing this up for me? I don’t want the Q values to change regardless of the frequency. Oh by the way, if possible, perhaps you could add a choice between all harmonics to notch, even harmonics only, or odd harmonics only. Essentially the Dehum, but with the Q values the same across the width, and none of this stupid noiseegate business. Also, I’d suggest keeping it so it counts the harmonics automatically so no “number of harmonics”, it would just do it.


;nyquist plug-in
;version 1
;type process
;name “Note notcher.0”
;action “Removing harmonics, ( this may take some time ) …”
;info: Unintentional reverb effect. Gibbs ringing on transients.
;info: if you find a cure for the reverb please email me: uvw111-reverb@yahoo.co.uk

;control freq “Frequency” real “” 60 1 20000
;control q “Width” real " higher is more" 10 0.01 100
(setq mysound s)
(setq q q) ; set the base Q for the filter
(setq r sound-srate)
(setq iter (truncate (/ (/ r freq) 2)))
(defun notch (mysound freq q iter))
(dotimes (i iter mysound))
(setf mysound (notch2 mysound (* freq (1+ i)))

(multichan-expand #'notch2 mysound freq q iter))

There has to at least be a count of the notches within the code - you don’t want it looping to infinity. So even if you don’t have a user control for the number of notches, you have to decide how many notches you want.

None of us have been taught to program in Nyquist either. As far as I’m aware, the only place that Nyquist is taught is at CMU (where Nyquist was invented). However, Nyquist is a relatively simple language, and it is pretty well documented these days (plus support on this forum).

There’s a reason for that.

The Q of a notch filter sets the width, which is a proportion of the centre frequency. For example, for Q = 1, the width is about +/- 1 octave of the centre frequency.
For easy calculation, let’s say that Q = 1. That means that for a notch with a centre frequency of 100 Hz, the -3dB width of the notch is around 50 Hz to 200 Hz, a range of 150 Hz.
Now if we look at the notch centred at 1000 Hz, the notch is now around 500 Hz to 2000 Hz, a width of 1500 Hz. If the Q is not increased for higher frequency values, we end up taking out nearly all of the high frequency sound because the notches are so wide.

Also, the higher the Q, the more ringing occurs, so for hum removal there is a need to compromise between not removing too much, and not causing too much ringing.
For your application it may be acceptable to have a fixed Q for all notches, but narrower notches at higher frequencies tends to be best for hum removal.

Again, there’s a reason for that. It may not be appropriate for your application, but for hum removal it makes sense (not stupid) to minimise damage to the audio by only applying the notches where it is really needed.

Here’s some plug-in code for you. Feel free to modify it to suit your needs, and feel free to tell us about any such modifications:

;nyquist plug-in
;version 4
;type process
;name "Multi-Notch"
;action "Filtering (this may take some time) ..."
;author "Steve Daulton"
;copyright "Released under terms of the GNU General Public License version 2"

;control freq "Base frequency" float "Hz" 100 10 2000
;control hz-max "Maximum filter frequency" float "kHz" 10 1 20
;control mode "Which harmonics" choice "All,Even,Odd" 0
;control q "Filter Q" real " higher narrower" 10 0.01 100

;; notch frequency must be less than (/ *sound-srate* 2.0)
(setf hz-max (min (* 1000 hz-max)(/ *sound-srate* 2.0)))

(defun notch (sig)
  (setf sig (notch2 sig freq q))
  (setq iterations (/ hz-max freq))
  (case mode
    (0  (setf hz (* freq 2))
        (setf step 1)
        (setf multiplier 2))
    (1  (setf hz (* freq 2))
        (setf step 2)
        (setf multiplier 2))
    (t  (setf hz (* freq 3))
        (setf step 2)
        (setf multiplier 3)))
  (do ((hz hz (* freq multiplier)))
      ((>= hz hz-max) sig)
    ;(print hz)
    (setf sig (notch2 sig hz q))
    (setf multiplier (+ multiplier step))))

(multichan-expand #'notch *track*)

It’s not showing up in my Effect menu even after restarting Audacity. I did save it as a plugin, but apparently it doesn’t show up for some reason. Maybe because it uses version 4 syntax and I still use 2.0.6.
Hmm - let’s see - the only differences I’m aware of are from version 3 and version 4, is that “S” in version 3 is replaced by “track” in version 4. I edited that so it would reflect that, plus changed the version to version 3. My guess is that “Sig” isn’t recognized or something? Now if only it would show up. Reading the code, however, it looks quite promising.

It’s still not showing up! HELP PLEASE!
Groupnotch.ny (1.1 KB)

Well, my friend helped me figure it out why it wasn’t showing up. He looked at the Log, and it says that it couldn’t load the plugin because it wasn’t Unicode when I copied in to a TXT file and saved it as a Ny.
After trying your original as well as my slightly modified version, there’s still a bug and I can’t pinpoint what it is.
Here’s the Debug output with the default settings.
“Nyquist returned the value: 10000”

Your final line is wrong.
You have this:

(multichan-expand #'notch2 s freq q)

but it should be “notch” and not “notch2”.

That still causes a bug! Something must be wrong with Hz-Max I guess? I’ll attach the bug.
Bugs.txt (61.4 KB)

Why don’t you just update Audacity to the current version? You can get it here: https://www.audacityteam.org/download/

Well, I’ve been told that Audacity 2.1.x has a really bad effect interface where it must insert unwanted silence whenever you apply an effect. To me, this is absolutely stupid and the only reason I’m not ever going to try it. Why risk the chance of inserting unwanted silence for no reason?
BTW, I’ve download Nyquistide just to try it out, and all there is is a text area, nothing else. Whenever I switch to a different task, it just flat-out closes. Why is nothing accessible anymore?

Well, even on a friend’s computer that has Audacity version 2.1.2, the plugin still won’t run. There’s still a bug! This is totally IMPOSSIBLE for me to figure out!
;nyquist plug-in
;version 3
;type process
;name “Group notch”
;action “Filtering (this may take some time) …”
;author “Steve Daulton”
;copyright “Released under terms of the GNU General Public License version 2”

;control freq “Base frequency” real “Hz” 100 10 2000
;control mode “Which harmonics” choice “All,Even,Odd” 0
;control q “Filter Q” real " higher narrower" 10 0.01 100

;; notch frequency must be less than (/ sound-srate 2.0)

(defun notch (s)
(setq s (notch s freq mode q))
(setq iterations (/ (/ sound-srate 2.0) freq))
(case mode
(0 (setq hz (* freq 2))
(setq step 1)
(setq multiplier 2))
(1 (setq hz (* freq 2))
(setq step 2)
(setq multiplier 2))
(t (setq hz (* freq 3))
(setq step 2)
(setq multiplier 3)))
(do ((hz hz (* freq multiplier)))
((>= / sound-srate 2.0) s)
;(print hz)
(setq s (notch s hz q))
(setq multiplier (+ multiplier step))))

(multichan-expand #'notch s freq mode q)

I don’t know who told you that, or what they were referring to, but we are way past 2.1.x. The current version of Audacity is 2.2.2 and it is available here: Audacity ® | Downloads

Try clicking the debug button rather than the OK button. That will open an additional window after the effect has been run, containing any error messages that the plug-in produces. Or better, upgrade to Audacity 2.2.2 and install the plug-in that I wrote.

Here’s my version as a downloadable file:
multinotch.ny (1.07 KB)

Well, I got it to work, but @Steve I didn’t base the plugin after what you wrote. Instead, Trebor’s plugin. I can attach that here.
Also, I’m working on an EQ plugin that does the exact same thing based on the one-band EQ, but it too isn’t working. The only thing the bug says is:
error: unexpected EOF
Function: #<Subr-(null): #dcee170>
Arguments:
#<File-Stream: #db8d378>
#(
1>

I’ll attach the EQ plugin.

Michael

That most likely means that your “(” do not match your “)”.
The End Of Form (EOF) is reached before all the “(” have matching “)”.

Here’s the EQ-loop plugin that I tried to create based on the notch-loop one. Unfortunately I still haven’t figured out that bug yet.
Groupeq.ny (2.03 KB)

;version 1

>

No need to go all the way back to version 1. Audacity 2.0.6 fully supports version 3 plug-ins.
Better to use:

```text
;version 3



;control f “Center frequency” real “Hz” 440.0 20.0 20000.0
;control width “Bandwidth” real “octaves” 1.0 0.01 5.0

>

The ranges of these controls are excessive.
Your code is intended to apply multiple filters at harmonic intervals. For the default Audacity sample rate of 44100 Hz, any frequency above 11025 Hz will not have any harmonics before the physical limit of half the sample rate. Also a 5 octave bandwidth is going to stretch across multiple harmonics and 2.5 octaves below 22 kHz is less than 4 kHz. Generous but more reasonable ranges might be something like:

```text
;control f "Center frequency" real "Hz" 440.0 20.0 4000.0 
;control width "Bandwidth" real "octaves" 0.1 0.01 1.0



;control amplify “Apply amplification” int “0=no 1=yes” 1 0 1

>

No need to use an "int" widget when a "choice" widget would be more appropriate. The multi-choice widget is supported in version 3 plug-ins and later.
Better to use:

```text
;control amplify "Apply amplification" choice "No, Yes" 1



(setf x (if (not (arrayp signal))

>

Why are you setting a value for "x"? You don't use the variable "x" anywhere.
And why are you testing for "not" an array? Better to test if it "is" an array, so rather than saying:
"If signal is not an array, do A, otherwise do B"
you can say:
"if signal is an array, do B, otherwise do A"

Your function "normalize" does not actually normalize. It just finds the peak level of the audio. If the audio is mono, the function returns the peak level. If the audio is stereo, it returns the maximum of the levels of each channel. This function would be better written as:

```text
(defun peak-level (signal level)
  (if (arrayp signal)
      (max (peak (aref signal 0) ny:all)
           (peak (aref signal 1) ny:all))
      (peak signal ny:all)))



(setq mysound s)

>

Why are you creating a global variable "mysound"? In version 3 plug-ins, "S" is a global variable holding the selected audio. It is unnecessary to create a new global variable that has the same value as "S".
\
<br>
> ```text
(setq r *sound-srate*)

Why are you creating a global variable “r”? You already have a global variable sound-srate which you can use directly, so your line:

(setq iter (truncate (/ (/ r f) 2)))

could be written as:

(setq iter (truncate (/ (/ *sound-srate* f) 2)))

but what is “iter”?
The name suggests that it is an iterator, but you have set it to half of (sample-rate / centre-frequency).
I presume that you want this value so that you can limit the number of times that you loop the filter - in other words, you want to know how many harmonics of “f” you can have without exceeding (sample-rate / 2).
Better to write this as:

(setq number-of-harmonics (truncate (/ (/ *sound-srate* 2) f)))



(defun EQ (mysound f iter)

>

"EQ" is a keyword in LISP (see: http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-099.htm)
While it is not an error to reuse it as a variable, it is potentially confusing to do so. Better to use a more descriptive name such as "equalize".
The variable "iter" has a confusing name because you are not using it as an iterator, but simply an integer for the maximum number of loops. Less confusing to call it something like "num" (an abbreviation of "number"), so I would write that line as something like:

```text
(defun equalize (mysound hz num)



(eq-band mysound (* f (1+ i)) (* width) (* gain))

>

OK, so here you are using the function (eq-band signal hz gain width) , but why have you used (* width) and (* gain)? That is meaningless.
As "width" and "gain" are global variables, set by controls, you can simply write:

```text
(eq-band mysound (* f (1+ i)) gain width))))

So here is that function rewritten:

(defun equalize (mysound hz num)
  (dotimes (i num mysound) 
    (setf mysound
      (eq-band mysound (* f (1+ i)) gain width))))

Note that the order of parameters is important. If you call eq-band with (eq-band signal hz width gain), then you will probably get an error, and certainly get the wrong result because the width and gain parameters are the wrong way round. Note also that “DOTIMES” counts from 0 to NUM, which means that the final loop will have a frequency greater than half the sample rate, which is an error. To avoid that error, use:

(dotimes (i (- num 1) mysound)

This next part has multiple problems:

; applying EQ
(if (= amplify 0)
(eq-band mysound f gain width)
(normalize (eq-band mysound f gain width) level))

>

I assume that what you are wanting to do, is to apply normalization to the processed audio if "Apply amplification" is set to "yes", but it does not do that. If the test (= amplify 0) is true, then this code applies EQ-BAND, but surely you want it to apply your looped filter which you called "EQ"?
If the test is false, then this code runs the function "normalize" (which as explained above does not actually normalize anything), and uses EQ-BAND as the "sound" parameter, whereas I expect that you actually want to use your "EQ" filter. So we could write this code like this, assuming that we fix the "normalize" function to actually normalize:

```text
(if (= amplify)
    (normalize (equalize mysound f number-of-harmonics) level)
    (equalize mysound f number-of-harmonics))

and we can correct the normalize function like this:

(defun normalize (signal level)
  (setf peak-level
    (if (arrayp signal)
        (max (peak (aref signal 0) ny:all)
             (peak (aref signal 1) ny:all))
        (peak signal ny:all)))
  (mult (/ level peak-level) signal))

The final part of your code:

(vector (eq (aref s 0) f iter)
(eq (aref s 1) f iter))
(eq s f iter))

>

This is unnecessary (and won't work as intended). As with most of the high level processing functions, the EQ-BAND function works with either mono or stereo sound.

So here's the full plug-in as code, and at the bottom below is a slightly tidier version as a downloadable file.

```text
;nyquist plug-in
;version 3
;type process
;name "Group EQ"
;action "Removing harmonics, ( this may take some time ) ..."

;control fc "Center frequency" real "Hz" 440.0 20.0 4000.0 
;control width "Bandwidth" real "octaves" 0.1 0.01 1.0 
;control gain "Gain" real "db" 0.0 -48.0 48.0
;control amplify "Apply amplification" choice "Yes,No" 0
;control level "Level" real "linear" 1.0 0.0 1.0

(defun normalize (signal level)
  (setf peak-level
    (if (arrayp signal)
        (max (peak (aref signal 0) ny:all)
             (peak (aref signal 1) ny:all))
        (peak signal ny:all)))
  (mult (/ level peak-level) signal))

(defun equalize (mysound hz num)
  (dotimes (i (1- num) mysound)
    (setf mysound
      (eq-band mysound (* hz (1+ i)) gain width))))

(setq number-of-harmonics (truncate (/ (/ *sound-srate* 2) fc)))

(if (= amplify 0)
    (normalize (equalize S fc number-of-harmonics) level)
    (equalize S fc number-of-harmonics))

Groupeq.ny (1.71 KB)

Well, I think there might be an error in the loop. Thing is, if I type in 100 for the frequency, it only changes 100, 300, 600, 2400, etc. My goal is for it to change 100, 200, 300, 400, etc etc. In the Notch plugin, the loop worked just fine:
(dotimes (i iter mysound)
(setf mysound (notch2 mysound (* freq (1+ i)) (* q)))))
And the EQ loop is so different I’m not sure how to make it so it just adds the numbers up.

(do* ((i 1 (1+ i))
For some reason it multiplies by 3. Not sure why that is.

Not if you are using the code that I posted.
Here I’ve added a line to print the centre frequency of the filter. Run this with the debug button to see a list of the frequencies:

;nyquist plug-in
;version 3
;type process
;name "Group EQ"
;action "Removing harmonics, ( this may take some time ) ..."

;control fc "Center frequency" real "Hz" 440.0 20.0 4000.0
;control width "Bandwidth" real "octaves" 0.1 0.01 1.0
;control gain "Gain" real "db" 0.0 -48.0 48.0
;control amplify "Apply amplification" choice "Yes,No" 0
;control level "Level" real "linear" 1.0 0.0 1.0

(defun normalize (signal level)
  (setf peak-level
    (if (arrayp signal)
        (max (peak (aref signal 0) ny:all)
             (peak (aref signal 1) ny:all))
        (peak signal ny:all)))
  (mult (/ level peak-level) signal))

(defun equalize (mysound hz num)
  (dotimes (i (1- num) mysound)
    (format t "Frequency ~a~%" (* hz (1+ i)))
    (setf mysound
      (eq-band mysound (* hz (1+ i)) gain width))))

(setq number-of-harmonics (truncate (/ (/ *sound-srate* 2) fc)))

(if (= amplify 0)
    (normalize (equalize S fc number-of-harmonics) level)
    (equalize S fc number-of-harmonics))

Well, thanks for the code, but I’ve heard there is a serious issue simply copying the code straight off a page into a plugin. Thing is, any plugin just copied from there simply says “Error: Illegal character”. I have no idea why that is though. Perhaps I should save it in Notepad++ as Lisp format or something?