Before diving into the Nyquist programming language, we will first take a quick look at what programming is. A “program” is a set of steps to produce a result. Typically when we talk of programming we mean programming a computer, but in a broader sense we can consider any ordered set of steps to produce a result, such as a cooking recipe.
A cooking recipe begins by defining the ingredients (flour, milk, eggs,…), and the exact quantities of each, (weight of flour, volume milk, number of eggs, …), followed by a sequence of steps (add the milk to the flour a little at a time …). This is similar to the simplest kind of programming in which a list of commands produces a result. A good example of this simplest form of programming is Audacity’s Macros.
Audacity Macro Example
“Fade Ends” macro
Audacity ships with a few simple macros, including a macro to fade the ends of a track:
1 2 3 4 5 6 |
Select:Start="0" End="1" FadeIn:Use_Preset="<Factory Defaults>" Select:Start="0" End="1" RelativeTo="ProjectEnd" FadeOut:Use_Preset="<Factory Defaults>" Select:Start="0" End="0" |
The macro has 5 lines with one command per line. When the macro runs, each command is performed in turn:
- Select: Select from time=0 to time=1 (seconds)
- FadeIn: Apply the Fade In effect
- Select: Make a new selection, this time relative to the end of the project. Select from the end of the project to 1 second before the end.
- FadeOut: Apply the Fade Out effect
- Select: and finally place the cursor at the start (select from 0 to 0).
These 5 steps will produce a predictable result, that is to fade in the first 1 second and fade out the final 1 second.
While useful, Audacity’s Macros are limited by the fact that they are just a list of commands. We can’t, for example, tell a macro to fade the first and final 1 second when the track is less than 30 seconds long, and fade 5 seconds for longer tracks. To do that we would need a more sophisticated language that perform conditional branching. More about this later, but here’s a pseudo-code example to give an idea:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
GET LENGTH of track IF track length LESS THAN 30 seconds THEN SELECT first 1 second FADE IN SELECT final 1 second FADE OUT ELSE SELECT first 5 second FADE IN SELECT final 5 second FADE OUT |
In the above example, we test for a certain condition; “is the length less than 30 seconds“.
If the answer is Yes / True, then the instructions follow one path. If No / False, then the instructions follow the other path.
Computers are stupid
In these days when stories of artificial intelligence are all the rage, it’s easy to get the impression that computers are very clever, but they’re not. Computers can appear to be smart because they are running very clever programs, but the computer itself is as dumb as dumb can be. Try it for yourself; tell your computer to make a cup of coffee. Notice that your computer just sits there and does nothing. Even if your computer had arms, legs, ears and eyes like a futuristic robot, it would still do nothing because:
- It doesn’t know what coffee is
- It doesn’t know what a cup is
- It doesn’t know what “go and make” means
- …
For the computer to be able to do anything at all, it needs complete instructions. It needs every item involved in the task to be clearly and unambiguously defined. It needs every step to be specified exactly, and it needs these instructions to be in a language that it understands.
In the early days of computers, a programming language might look something like this:
1 2 3 4 5 6 7 8 9 |
SECTION .TEXT GLOBAL MAIN MAIN: MOV EDX,LEN MOV ECX,MSG MOV EBX,1 MOV EAX,4 INT 0X80 |
Fortunately, programming languages have come a long way and now have some resemblance to languages that humans can understand (predominantly English). However, although modern “high level” computer languages may resemble English, it is important to remember that it is not English. Unlike computers, humans are very smart, much smarter than any computer. If I say “I eat this apple”, or “this apple I eat”, or even “apple this I eat”, you understand that “I” (the first person singular) is “eating” (an action that you are familiar with), an “apple” (a familiar object that is being eaten). For a computer, it would first be necessary to define exactly what “I”, the “apple” and “eating” are, and then to construct the statement in strict accordance to the rules of the language (the programming language syntax).
Nyquist Syntax
In computer jargon, Nyquist, a member of the LISP family of programming languages, is written as “s-expressions in fully parenthesized prefix notation“. What that means in plain English is that each command is enclosed in parentheses “(” and “)”, with the command name first, followed by a list of arguments (parameters) that are required by the command. Whereas in English we might say “David drives the car”, in LISP like languages we would write (drive car David) – the operation (driving) comes first, followed by the two things (the car and David) listed after, and the entirety enclosed in parentheses.
Results and side effects
Previously I described a program as a set of steps to produce a result. In the case of Nyquist in Audacity, there is always exactly one “result”, which in Nyquist jargon is called “the return value”. The result from running a script / program is returned (passed back) to Audacity. Result that Audacity considers “valid” may be:
- a sound, which Audacity will attempt to put into an audio track.
- text (a “string“), which Audacity will display in a message window.
- a number, which Audacity will display in a message window (as it does for text).
and some “special cases”:
- a specially formed list, from which Audacity can create labels.
- an empty string, which Audacity treats as a “no operation” command (do nothing).
Any other return value is considered an error.
For most plug-ins, the return value is the entire purpose, the raison d’être of the plug-in. However, there are some cases where we want a plug-in to do something other than returning sound / text / numbers / labels. The Sample Data Export plug-in, for example, writes data to a file. In such cases, the code is said to have “side effects”, which are produced as the code runs. The code will still have a return value, even if only a “no-op” (or an error), but there is always a result of some type that gets returned to Audacity. If the return value is not seen by Audacity as a valid value, then it’s an error that should be considered to be a bug.
A few simple examples
These examples may be run in Audacity’s Nyquist Prompt effect.
Adding a list of numbers
We are all familiar with the arithmetic notation for adding numbers, such as:
1 + 2 + 3 + 4
In Nyquist, the notation is a bit different, and is more like how we would say “add 1, 2, 3 and 4”. The operation (adding) comes first, followed by a list of arguments (the numbers to add), and the whole command is enclosed in parentheses, like this:
1 2 |
(+ 1 2 3 4 5) |
If you run the above code, you should see the result:
1 2 |
Nyquist returned the value: 15 |
Other arithmetic operations are similar:
1 2 |
(mult 2 4) |
1 2 |
(/ 42 2) |
And we can combine operations, for example, to add a list of numbers and divide by 2:
1 2 |
(/ (+ 1 2 3 4 5) 2) |
Notice in the above example, the addition is the inner-most operation and is done first, then the division by two is done.
Notice also that the result is the integer (whole number) 7, and not 7.5. This is because all of the values are integers, so Nyquist performs integer arithmetic, giving an integer result. If we want the exact fractional result, then we must ensure that at least one of the arguments is specified as a decimal value. For example:
1 2 |
(/ (+ 1 2 3 4 5) 2.0) |
An audio example
Given that the Nyquist language was created specifically for working with sounds, it would be remiss of me to not give a couple of audio examples before wrapping up this introduction. So let’s start with generating a simple sine tone. To do this we will use Nyquist’s built-in function “HZOSC”, and generate a 1000 Hz sine tone. The command name “hzosc” comes first, then the argument / parameter “1000” (Hz), and the entire command enclosed in parentheses:
1 2 |
(hzosc 1000) |
Notice that the sine tone is the full height of the track (0 dB). If we want a sine tone that is only half the height of the track (around -6 dB), then we can scale it:
1 2 |
(scale 0.5 (hzosc 1000)) |
Wrapping up
Nyquist is a full featured programming language for working with audio. Unlike Audacity’s macros it is not limited to just lists of instructions, but can perform much more complex operations, including conditional branching (this will be covered in a later tutorial).
Nyquist has a simple syntax, that may be generalised as:
(operation argument1 argument2 ...)
and commands may be nested:
(op2 (op1 arg1 arg2 ...) arg3)
Audacity provides a handy tool, the Nyquist Prompt, which allows us to quickly and easily test small Nyquist scripts.