Using Standalone Nyquist with existing plugins

Hi Steve,

Perhaps you can answer this, is it possible to…
Using the standalone version of Nyquist (ver 3.12 XLISP ver 2.0 and CLI on Linux), import a wav file,
add an effect using one of Audacity’s .ny plugins and then saving the resulting wav?

I have tried the following but it always errors out with a:
“error: sound save: expression did not return a sound - NIL”

(setf plug-in "/usr/share/audacity/plug-ins/highpass.ny")
(setf in-file "/path/to/in-file.wav")
(setf out-file "/path/to/out-file.wav")

; Set plug-in parameters - var names same as in highpass.ny
(setf frequency 1000)
(setf rolloff 4)

; Populate the properties
(setf *track* (s-read in-file))
(setf len (snd-length (aref *track* 0) 100000000))

; Import the plug-in code

(defun process-it ()
(do* ((fp (open plug-in :direction :input))
      (ex (read fp nil) (read fp nil)))
  ((null ex) (close fp) nil)
  (eval ex)))

; Process and store the audio
(s-save (process-it) len out-file)

(exit)

I then save the above as Paul-1.ny and run it as such:
ny -l Paul-1.ny

However, if I change the s-save line to:

(s-save (aref *track* 0) len out-file)

Then it does create the output wav, but no filtering action has taken place.
Another interesting thing, if I set rolloff higher than 4, then it errors.
This is a good thing as there are only 4 options, so it tells me it is seeing the
highpass.ny plugin code.

Thanks,
Paul

Not without substantial modifications to the plug-in code.
“Nyquist Plug-ins” are specific to Audacity.
Audacity provides a lot of things that allow plug-ins to work, including:

  • Effects iterate across selected tracks. This is handled entirely by Audacity.
  • For those plug-ins that have a GUI, the GUI is created by Audacity.
  • Nyquist plug-in “headers” are an Audacity feature (as far as Nyquist is concerned, they are just “comments” and so ignored).
  • Many of the global variables listed here are set by Audacity.
  • All of these Global Property Lists are provided by Audacity: Missing features - Audacity Support
  • In fact, nearly everything on this page is specific to Audacity: https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
  • For “;type process” plug-ins, the environment is set such that the selection length (“real time”) is mapped to 1 second of “logical time”.

However, the processing code could be used.
Example, in this plug-in: https://github.com/audacity/audacity/blob/master/plug-ins/highpass.ny you could use this part of the code:

(cond
  ((< frequency 0.1) (_ "Frequency must be at least 0.1 Hz."))
  ((>= frequency (/ *sound-srate* 2.0))
      (format nil (_ "Error:~%~%Frequency (~a Hz) is too high for track sample rate.~%~%~
                   Track sample rate is ~a Hz~%~
                   Frequency must be less than ~a Hz.")
              frequency
              *sound-srate*
              (/ *sound-srate* 2.0)))
  (T  (funcall (nth rolloff '(hp highpass2 highpass4 highpass6 highpass8))
               *track* frequency)))

but you would need to set the values of ‘frequency’ and ‘rolloff’, and set ‘track’ to the imported sound. So really you are just using Nyquist’s high-pass filter functions.

Thank you Steve.

OK, not a big problem then as some of the limitations are not a problem, for my application anyways.
Things like length of selection, GUI and iterating across several tracks are not required anyhow.

As you say, as long as the parameters are populated before and track as well, with the audio data,
then the code can be used.

It would be nice just to use the plugins as is, but not serious if the headers need to be stripped off
and only the actual code is executed.
Can still be very useful for command line audio processing, especially when called from a Bash script.

Under Linux, GTKdialog could even be used to wrap the Bash script/s in a simple GUI.
GTKdialog scripts, are not compiled, but rather use a xml like structure, nice and simple.
The link below is specific to Puppylinux, but can be compiled for just about any distro.

This then brings me to another question, does the standalone Nyquist support piping?
Could I pipe audio data from say ffmpeg directly to Nyquist?

I rarely use stand-alone Nyquist, so I’m no expert with this, but I guess that Nyquist may be able to read audio from a named pipe in the same way as reading from a file (See: Appendix 3: XLISP: An Object-oriented Lisp and Appendix 3: XLISP: An Object-oriented Lisp).
Try it and see if you can get it to work :wink:

Tried several things but couldn’t get the piping to work.
Don’t really know if I’m doing something stupid, or, Nyquist does not support it.

No matter, my work around for now is to use temp files and then delete them when no longer required.

Thanks for all your tips.

Yes, that seems straightforward using S-READ.

Hi Steve,
A question, in Audacity there is a built-in called “rms” and “peak”, which return exactly what they describe.
Now, the problem I have is, using the stand-alone Nyquist version, these are not available.

Working out peak is a piece of cake:

(linear-to-db  (peak (aref *track* 0) 1000000000))

However, rms is the more complicated part.
If it was a pure sine wave, rms would be easy to calculate, it’s just:
Peak X 0.7071

However, for anything else, the RMS level is proportional to the amount of energy over a period of time in the signal.
Any pointers on how I could do this in “pure” Xlisp (Nyquist) ?

Once I get the “mono” rms, to work out stereo, it’s basically the root mean of all the the samples ^ 2.

Thanks.

EDIT:

Found that Nyquist does have a rms as well, but it acts on the actual audio instead of just calculating it:

(rms sound [rate window-size]) [LISP]

Computes the RMS of sound using a square window of size window-size.
The result has a sample rate of rate.
The default value of rate is 100 Hz, and the default window size is 1/rate seconds (converted to samples).
The sound is a SOUND or a multichannel sound (in which case the result is a multichannel sound).
The rate is a FLONUM and window-size is a FIXNUM.

Those are provided by Audacity, because Audacity already knows the peak and rms, so Nyquist can get that info from Audacity virtually instantly.

To calculate (mono) RMS with Nyquist, assuming that the sound is called “mysound” and is less than 10000000 samples:

(defun rmsdb (sig)
  (setf maxlength 10000000)
  (let ((step (snd-length sig maxlength))
        meansquare)
    (setf meansquare (snd-avg (mult sig sig) step step OP-AVERAGE))
    (linear-to-dB (sqrt (snd-fetch meansquare)))))

(print (rmsdb mysound))

Fantastic, thank you.
The 10 million sample limit is a bit of a hindrance, but can always analyze the audio in sections
and average it out.

Do you know the exact length of the sound?

Varies, but probably up to 90 minutes :astonished:
Most however, around 5 minutes max.

Sample rate can be 44.1 or 48 KHz and mono or stereo.

EDIT:

Or do you mean, can I get the length of the audio?
Yes I can.
Are you thinking of using the whole track as the measuring “window”?

For short sounds, the code that I posted previously is adequate. It uses SND-AVG (internally this is coded in C, so it’s fast and efficient) to calculate the average value of “step” number of samples, where “step” has been defined as the length of the audio in samples.

(snd-avg (mult sig sig) step step OP-AVERAGE)

The samples that it is averaging come from (mult sig sig).

(Checking the manual, I see that it recommends using S-AVG rather than SND-AVG, but in this case they are both essentially the same).

Since “step” is the length of the sound, SND-AVG (or S-AVG) returns one sample, which is the average of all the samples in (mult sig sig). So that gives us the mean square value as one sample. We then get the numeric value of that sample with SND-FETCH.

When S-AVG processes a block (“step” number) of samples, it does so in RAM, so there is a limit on how big a block can be processed.
I’ve just tested experimentally, and on my machine the maximum size is a bit over 2.1 million (so quite a bit short of my initial guess).

For calculating larger numbers of samples, we can use S-AVG to give us a series of samples, each of which is the mean square of one “step” size of samples. The final step will likely be incomplete as we don’t know the exact number of samples in advance, so we need to keep the step size small enough that the error in the final block will be relatively insignificant.

Example:
If our sound is 100 million samples, and we use a step size of 10000 samples, then that will loop 10000 times, returning 10000 samples.
We can then average those 10000 samples to get our overall average. The maximum error will be half a block of samples (500 samples), which if my arithmetic is correct is 0.0005%

(setf step 10000)
(setf ms (s-avg (mult mysound mysound) step step op-average)) ;returns 10000 samples
(setf ms (s-avg ms step step op-average))  ;returns 1 sample

or to make it more general:

(setf step 10000)
(setf ms (s-avg (mult mysound mysound) step step op-average)) ;returns unknown number of samples
(setf step (snd-length ms ny:all)) ; ny:all is a big  number
(setf ms (s-avg ms step step op-average))  ;returns 1 sample

Wow, a lot of info there to digest, will be going through it step by step (no pun intended).
At least now I can read Lisp and actually understand it, before you started helping me (several months ago),
you may as well have posted something like this:
Screen Shot 2021-10-14 at 11.28.24 PM.png
Thank you very much, I’m really enjoying Lisp, which BTW is 63 years old, the original version anyway.
Only Cobol is older by a year.