Coding Nyquist Effects for Audacity
Audacity is a popular and free DAW (digital audio workstation) that has been designed to support plug-ins that extend its functionality.
Audacity supports a bunch of plug-in types like LADSPA, LV2, VST, Vamp, Hi-Jacker and Nyquist. The plug-ins are either free or paid. Most of the plug-ins are designed to perform certain processing or analysis on the signal loaded in Audacity. Many a times you might be wanting to perform a very specific operation on audio signal for which there may not be a free/paid plug-in available. In such cases you my want to write your own plug-in.
In the past I have written VST plug-ins to implement and test audio algorithms. Recently I came across Nyquist plugins for Audacity. Though VST is neat and needs working knowledge of C/C++, Nyquist on the other hand is relatively simple. VST plug-ins will work with most of the DAW, however the same is not true about plug-ins developed using Nyquist. In this blog we shall look at how to build plug-ins using Nyquist programming.
Nyquist is a programming language for sound synthesis and analysis based on the Lisp programming language. It is an extension of the XLISP dialect of Lisp, and is named after Harry Nyquist. It can be used to write plug-in effects for Audacity. The Nyquist programming language and interpreter were written by Roger Dannenberg at Carnegie Mellon University.
Nyquist was contemporary with the birth of Audacity and is essentially Audacity’s own user-customizable plug-in format. Detailed technical information about Nyquist programming and plugins together with links to downloadable Nyquist plugins can be found in the manual: Nyquist.
Before we jump into writing plug-ins lets explore how to use the Nyquist prompt on Audacity for some basic audio processing.
To access the Nyquist prompt on Audacity, go to Tools > Nyquist Prompt
This opens up a Nyquist console like below:
Lets write and execute a simple Nyquist script to attenuate the audio in a chosen track by 6dB.
In this example we have a track which is 10s long with a tone of amplitude -6dBFS. We shall then choose the first 3s of the track and execute the following piece of line in Nyquist prompt.
(scale 0.5 *track*)
Pressing the OK button, modifies the first 3 seconds of the track as below:
Coding, Deploying and Testing a custom plug-in with Nyquist
Let's try building a simple plug-in that analyzes a stereo audio loaded into Audacity and returns the signal level in linear and dB scale.
The following four lines of information are mandatory for any Nyquist plug-ins.
;nyquist plug-in ;version 1 ;type analyze ;name "Signal Level Estimator"
Line-1: This header tells Audacity that this file is a Nyquist plug-in.
Line-2: This header specifies the version of the plug-in.
Line-3: This header defines the plug-in as a "Analyze" type.
Line-4: This header tells Audacity the name of the plug-in.
The other headers enable various options and properties, including the plug-in's controls.
;action "Finds the peak signal in linear and dB scale..." ;author "Vallabha Hampiholi" ;release 1.0.0 ;copyright "None"
The following piece of code analyzes the signal and prints the signal level in linear and dB scale.
(setq max-len 100000000) ; May be hardware dependent. (setq FMT-db "%#.1f") (setq FMT-val "%#.6f") (defun fmt-pretty (FMT &rest stuff) (progv '(*float-format*) (list FMT) (apply #'format stuff))) (fmt-pretty FMT-val nil "~a" 1.2345678) (defun fmt-peak (value) ; (strcat (fmt-db value) )) (defun fmt-db (value) ;print value and it's dB equivalent with appropriate formats (if (< value 0) (format nil "N/A") (strcat (fmt-pretty FMT-val nil "~a" value) (if (> value 0) (fmt-pretty FMT-db nil " (~a dB)" (linear-to-db value)) (format nil " (-inf dB)"))))) (defun analyze (s-in len) ; (setq lmax (snd-maxsamp s-in)) (setq lmax_db (linear-to-db lmax)) (format nil "Peak level:~%~a~%" (fmt-peak lmax)) ) (defun analyze-stereo (input len) ; for stereo tracks (format nil "Stereo track properties~%~%Left channel:~%~a~%~%Right channel:~%~a~%" (analyze (aref input 0) len) (analyze (aref input 1) len))) (format nil "~a~%Length of selection: ~a seconds.~%~a samples at ~a Hz.~%" (if (> len max-len) (format nil "Selection too long for analysis, please select shorter section~%") (if (arrayp s) (analyze-stereo s len) )) (get-duration 1) (truncate LEN) (truncate *sound-srate*))
The setq FMT-* sets the floating point precision of the results to be displayed post signal analysis.
Then there are bunch of user defined function like fmt-pretty, fmt-peak and fmt-db. These functions perform the operation of formatting the results, returning the linear and dB scale values for the input array and printing them on the screen.
The analyze function takes in the audio *s-in* and the length of audio *len* as selected in the track. The master function analyze calls the signal analysis for returning the linear and dB scale values.
analyze-stereo function internally parses the left (aref input 0) and right channel (aref input 1) and the selected length on audacity interface (len) and passes these parameters to the analyze function.
Deploying & Testing the plug-in
Once your plug-in is ready, you can drop it in the following path:
<Audacity Install Path>\Audacity\Plug-Ins
Lets name the above plug-in as SignalLevelEstimator.ny. Once the plug-in is dropped in the above folder, it needs to be enabled.
To enable the plug-in on the Audacity go to Effect > Add / Remove Plug-ins.
Select the New radio button. If all goes well, the plug-in named SignalLevelEstimator should appear on the list. Select the plug-in and click the enable button. The plug-in should now appear under the Analyze menu of Audacity like below.
As can be observed the plug-in like other function has been greyed out. The analyze functions can be used (or becomes active) when there is audio loaded. Let's create a stereo channel in which the left channel has a tone of -18dBFS (0.125 linear) and right channel has another tone at -6dBFS (0.5 linear) level and use the plug-in we just developed to analyze these signals.
Selecting the signal between 2s and 4s duration and executing the plug-in results in below analysis.
As can be observed the results of the plug-in are in line with the expected values.
Summary and Final Thoughts
Nyquist is a quick and easy way to develop and test simple algorithms for signal analysis using Audacity. Although the documentation and support community isn't as vibrant but once you get a hang of it, plug-in development can be pretty straight forward.
You can use such plug-in to replace day-to-day audio analysis you would normally do by reading data in tools like Python / MATLAB.
Although the tool lacks debugging capabilities while developing that you would normally find it easy with writing VST plug-ins, the Nyquist prompt can be used effectively for verifying your implementation before you merge the changes. Of course you could also use the XLisp tools to test the algorithm before deploying your functions as part of an audio effect.
References:
- https://www.cs.cmu.edu/~rbd/doc/nyquist/part8.html#index371
- https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
- https://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-index.htm