Filters: lp vs lowpass2

I was attempting to add a Q control to the existing Low Pass Filter (LFO) (lplfo.ny) effect. Simple enough, right? Just change the call from “lp” to “lowpass2” and add a Q setting! Nope, the Debug Output says:

error: In LOWPASS2, 2nd argument (hz) must be a positive number or array of positive numbers, got a SOUND

It looks like those two have a different type to the second argument:

lp(sound, cutoff); cutoff is a float or a signal
lowpass2(signal, hz [, q]) ; hz is a FLONUM

What am I missing?

  • Win

Do you have the manual: Index
In particular:

Note also that lplfo.ny is written in LISP syntax.
lp(sound, cutoff) is written in SAL (see: SAL)
In LISP syntax it would be (lp sound cutoff)

If you need a LPF with a steeper “skirt” and don’t want to mess with “Q”,
you can use a higher order filter as well:
Screen Shot 2021-11-02 at 12.31.22 AM.png
Make sure you use the correct syntax to match the plugin, either SAL or LISP.

Yes, that’s where I saw the differing specifications for lp and lowpass2.

Note also that > lplfo.ny > is written in LISP syntax.
lp(sound, cutoff) > is written in SAL (see: > SAL> )
In LISP syntax it would be > (lp sound cutoff)

I hadn’t noticed that difference, but I’m pretty sure my problem is not related to syntax:

WORKS:   (normalize (lp s (get-lfo f phase factor center)))
DOESN'T: (normalize (lowpass2 s (get-lfo f phase factor center) q))



Thanks, but actually, I do want to mess with “Q”! :slight_smile:

  • Win

Let’s rewrite that so that it is easier to see what the code is doing:

(let ((mysound (get-lfo f phase factor center)))
  (setf mysound (lp s mysound))
  (normalize mysound))

First line: create a local variable (called “mysound”) and let it have the value of the sound generated by “get-lfo”. Note that “get-lfo” returns a low frequency, low sample rate signal (a sound).

Second line: Filter the sound “S” with the frequency determined by “mysound”.
Note in the manual:

lp(sound, cutoff) [SAL]
(lp sound cutoff) [LISP]
Filters sound using a first-order Butterworth low-pass filter. > Cutoff may be a float or a signal (for time-varying filtering) > and expresses hertz. Filter coefficients (requiring trig functions) are recomputed at the sample rate of cutoff. The resulting sample rate, start time, etc. are taken from sound.

whereas, for “lowpass2”:

lowpass2(signal, hz [, q]) [SAL]
(lowpass2 signal hz [q]) [LISP]
A fixed-parameter, second-order lowpass filter based on snd-biquad. > The cutoff frequency is given by hz (a FLONUM) > and an optional Q factor is given by q (a FLONUM).

“lp” can take a number or a signal (a sound) as the frequency parameter, but “lowpass2” requires a (floating point) numeric value for the frequency parameter.
And the error message:

error: In LOWPASS2, 2nd argument (hz) must be > a positive number > or array of positive numbers, got a > SOUND


Re. “or array of positive numbers”:
It looks like the lowpass2 filter has been updated since that part of the manual was written. In current versions of Nyquist, an array of numbers may be used, producing a separate channel for each numeric frequency in the array.

Example (using Audacity’s version 4 syntax):
If you have a stereo track, and you want to filter the left and right channels at different frequencies, you can do this:

(setf left-hz 100)
(setf right-hz 4000)
(setf cut-off (vector left-hz right-hz))
(lowpass2 *track* cut-off)

or, more concisely:

(lowpass2 *track* #(100 4000))

I’m sure you can appreciate the frustration of having an out-of-date manual!

However, I don’t have a stereo track (yet), and even if I did, I don’t (yet) want to filter L+R at different frequencies. I want to use an LFO to modulate the filter’s frequency.

(lowpass2 *track* #(100 4000))

I don’t know what the # here means (couldn’t find it in the manual), but it seems to declare an array, which doesn’t apply to what I’m doing.

I note that the standard tremolo.ny uses the hz-to-step function. We’re clearly getting past my knowledge limits, but could that help here?

  • Win

Absolutely, and I’ve sent a message to Professor Dannenberg about the omission.

As I wrote,

(lowpass2 *track* #(100 4000))

is a concise (shorthand) way to write

(setf left-hz 100)
(setf right-hz 4000)
(setf cut-off (vector left-hz right-hz))
(lowpass2 *track* cut-off)

Explanation:

The “vector” function initialises a two dimensional array (a vector).
An alternative way to make a two dimensional array is with “make-array”, but then you have to initialise it yourself.

These two snippets are equivalent:

(setf myarray (vector 5 10 25))  ;create an array containing integers 5, 10 and 25
(print myarray)  ;prints #(5 10 25)



(setf myarray (make-array 3))
(setf (aref myarray 0) 5)
(setf (aref myarray 0) 10)
(setf (aref myarray 0) 25)
(print myarray)  ;prints #(5 10 25)

Observe how the array is printed as the hash symbol followed by the array elements within parentheses. In some dialects of LISP this is referred to as a “literal vector”. In some LISP dialects there is a subtle difference between writing #( elements) and writing (vector elements), but in Nyquist I think they are identical. Normally you would write the slightly longer version (vector elements).

That won’t help here.
https://www.cs.cmu.edu/~rbd/doc/nyquist/part8.html#index320

(hz-to-step freq) [LISP]
Returns a step number for freq (in hz), which can be either a number of a SOUND. > The result has the same type as the argument

So if “freq” is a number, then (hz-to-step freq) returns a number, and if “freq” is a sound, then (hz-to-step freq) returns a sound.

If you want to create a “peaky” response similar to LOWPASS2 with a high Q value, one possibility is to combine the LP filter with a EQ-BAND filter. Something like:

(defun mylowpass (sig hz boost)
  ;; A low-pass filter with dB boost at filter frequency.
  ;; hz must be signals at the control rate
  ;; boost must be a number.
  (let ((boost (const boost))
        (width (const 1)))
    (lp (eq-band sig hz boost width) hz)))


; Example control signal
(setf mysound (mult 500 (sum 2 (lfo 0.1))))

(setf mysound (mylowpass *track* mysound 6.0))

Ah, got it - I hadn’t understood the “step” concept, which is explained right at the top of the documentation! :slight_smile:

If you want to create a “peaky” response similar to LOWPASS2 with a high Q value, one possibility is to combine the LP filter with a EQ-BAND filter.

Interesting concept, that may be just what I need. Thanks!

  • Win