Nyquist frequency divider feedback not working

Using Nyquist scripts in Audacity.
Post and download new plug-ins.
Forum rules
If you require help using Audacity, please post on the forum board relevant to your operating system:
Windows
Mac OS X
GNU/Linux and Unix-like
zeropoint
Posts: 7
Joined: Sat May 19, 2018 4:12 am
Operating System: Windows 7

Nyquist frequency divider feedback not working

Post by zeropoint » Sat May 19, 2018 4:37 am

Hi all,

I'm new to Nyquist, though I have done some DSP programming before. I have the following code working very well in Faust DSP, but am having a lot of trouble getting it to work in Nyquist. It's basically a type of mixer/oscillator that precisely divides an audio frequency by two.

The code divides a frequency by two by mixing it with the downmixed output. In this case a 16kHz sinewave input is mixed with the 8Khz downmixed output by feeding the filtered 8kHz output signal back and multiplying it with the 16kHz input to produce the 8kHz difference. This requires the previous output sample of the routine to be sent back to the first stage for multiplication. So far I haven't found a way to do this in Nyquist that actually works.

The dither ensures the process starts reliably. The working routine needs a limiter in the feedback path to prevent overload. This has been omitted from the example below for simplicity.

It appears as if there is no feedback happening. If the main expression is changed to:
set d = bandpass2(a * (0 + (noise() * dither-level)),8000,100) * 10
.. it produces the same result.

I suspect this is due to Nyquist's "Lazy evaluation".. but how can it be made to work?

The code is shown below.

Code: Select all

set a = hzosc(16000,*table*,0)		;generate a 16kHz sinewave input signal for testing

set ditherlevel = db-to-linear(-50)	;set dither level

if ! fboundp(quote(d)) then set d = 0	;set d to zero only if d is unbound

set d = bandpass2(a * (d + (noise() * ditherlevel)),8000,100) * 10	;d = BPF(a * (d + dither)) * 10

return d 

steve
Site Admin
Posts: 47305
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu
Contact:

Re: Nyquist frequency divider feedback not working

Post by steve » Sat May 19, 2018 9:39 am

Try multiplying by 1000 instead of by 10. in the 4th line (or use a much higher level of noise).
Is that what you want it to do?
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

zeropoint
Posts: 7
Joined: Sat May 19, 2018 4:12 am
Operating System: Windows 7

Re: Nyquist frequency divider feedback not working

Post by zeropoint » Sat May 19, 2018 10:10 am

No, not at all.

Boosting the gain by increasing the multiplier just gives you higher level filtered noise (dither).

The noise is only there to get things started (like in an analog oscillator). If there was no dither used, there would initially be no output from multiplying a and d. In the Faust DSP language on a 64 bit PC the dither level can be set to -300dB and the code still produces a full scale sinewave output, requiring AGC/Limiting to avoid distortion.

If the routine is working correctly, the Q multiplier effect of the feedback produces a very pure 8kHz sinewave that rapidly grows in amplitude until filter overload occurs. This is not happening.

Swapping d for 0 in the main expression makes no difference. There appears to be no feedback happening. Am I using the variable "d" correctly?

One small correction.. in the text above the code "dither-level" should be "ditherlevel".

steve
Site Admin
Posts: 47305
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu
Contact:

Re: Nyquist frequency divider feedback not working

Post by steve » Sat May 19, 2018 10:22 am

In the BANDPASS2 implementation, the biquad parameters are defined to keep the filter stable even at very high Q values.
If you want the filter to become unstable and go into self oscillation, then I think you will need to calculate the biquad parameters yourself and use BIQUAD or BIQUAD-M functions rather than the BANDPASS2. See: http://www.cs.cmu.edu/~rbd/doc/nyquist/ ... l#index460
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

steve
Site Admin
Posts: 47305
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu
Contact:

Re: Nyquist frequency divider feedback not working

Post by steve » Sat May 19, 2018 10:47 am

steve wrote:If you want the filter to become unstable and go into self oscillation, then I think you will need to calculate the biquad parameters yourself and use BIQUAD or BIQUAD-M functions rather than the BANDPASS2.
On reflection, that's not going to work. The biquad function is protected against becoming unstable and will throw an error.

The error checking for the biquad filter is in the function NYQ:BIQUAD.
You can redefine NYQ:BIQUAD in your own code without the error checking, and then you will able to use BIQUAD or BIQUAD-M with unstable parameters.
This is the definition of NYQ:BIQUAD that you need (it's in LISP format as I'm not very familiar with SAL)

Code: Select all

(defun nyq:biquad (x b0 b1 b2 a0 a1 a2)
  (if (<= a0 0.0)
      (error (format nil "a0 < 0 (unstable parameter a0 = ~A) in biquad~%" a0)))
  (let ((a0r (/ 1.0 a0)))
    (setf a1 (* a0r a1)
          a2 (* a0r a2))
    (snd-biquad x (* a0r b0) (* a0r b1) (* a0r b2)
                  a1 a2 0 0)))
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

steve
Site Admin
Posts: 47305
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu
Contact:

Re: Nyquist frequency divider feedback not working

Post by steve » Sat May 19, 2018 11:25 am

9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

zeropoint
Posts: 7
Joined: Sat May 19, 2018 4:12 am
Operating System: Windows 7

Re: Nyquist frequency divider feedback not working

Post by zeropoint » Sat May 19, 2018 11:39 am

Filter stability refers to the internal filter operation (whether there is output with no input). The filter used here is required to be stable, or else the output frequency would be uncontrolled, and also there would be no way to apply AGC to prevent distortion.

So back to the original question.. why isn't the variable "d" providing feedback? Would "d" in the expression always be equal to the previous output "d" sample value? (it needs to be for the routine to work) Is Nyquist always evaluating the "d" variable in the expression, or is it evaluating it once only?

Is there a way to make sure Nyquist is re-evaluating "d" in the expression for each sample value passing through the routine?

steve
Site Admin
Posts: 47305
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu
Contact:

Re: Nyquist frequency divider feedback not working

Post by steve » Sat May 19, 2018 12:04 pm

This code:

Code: Select all

set d = bandpass2(a * (d + (noise() * ditherlevel)),8000,100) * 10   ;d = BPF(a * (d + dither)) * 10
could be written as:

Code: Select all

set input = a * (d + (noise() * ditherlevel))
set d = bandpass2(input, 8000, 100) * 10
In fact, your code is identical to this:

Code: Select all

set input = hzosc(16000) * (0 + (noise() * db-to-linear(-50)))
return bandpass2(input, 8000, 100) * 10
or as one line:

Code: Select all

return bandpass2( hzosc(1600) * noise() * db-to-linear(-50), 8000, 100) * 10
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

steve
Site Admin
Posts: 47305
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu
Contact:

Re: Nyquist frequency divider feedback not working

Post by steve » Sat May 19, 2018 1:06 pm

The Nyquist filter:

Code: Select all

bandpass2(<sound>, 8000, 100)
is equivalent to the Matlab biquad filter with parameters:

Code: Select all

b0: 0.00454278
b1: 0
b2: -0.00454278
a0: 1.00454
a1: -0.83554
a2: 0.995457
so your code could be written as (in SAL):

Code: Select all

set b0 = 0.00454278
set b1 = 0
set b2 = -0.00454278
set a0 = 1.00454
set a1 = -0.83554
set a2 = 0.995457
set input = hzosc(16000) * (noise() * db-to-linear(-50))
return biquad-m(input, b0, b1, b2, a0, a1, a2) * 10
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

zeropoint
Posts: 7
Joined: Sat May 19, 2018 4:12 am
Operating System: Windows 7

Re: Nyquist frequency divider feedback not working

Post by zeropoint » Sat May 19, 2018 2:11 pm

Those later examples have no feedback path, so they can't perform the required function.

To generate output at 8kHz from a 16kHz input, the 16kHz signal has to be multiplied with an 8kHz signal to perform frequency mixing.

To ensure the output is exactly half the input frequency, the 8kHz mixing signal needs to be from the 8kHz output signal. This requires a path from the signal output back to the signal input.

In Faust DSP, you can't do this by just using the variable "d", as it flags an infinite loop error. Instead, the recursion operator "~" and the wire connection operators "_" must be used. How would this be done in Nyquist?

Here is a Faust DSP routine that actually works. (It has some aliasing distortion due to the simplified limiter code.) It can be run using the Faust online editor. Be warned, it produces a loud 8kHz audio tone when run.

Code: Select all

import("stdfaust.lib");
process = d;
			
dither = no.noise * ba.db2linear(-300); //dither at -300dB is adequate for 64 bit systems
a = os.oscp(16000,0); //16kHz sinewave input test signal
d = (a * (_ + dither):fi.bandpass(2,7950,8050):limit) ~_; //frequency divider by mixing
limit(x) = x*(0.2/max(abs(x),0.09)); //simple limiter to avoid massive filter overload

Post Reply