## Adding sounds, numbers and vectors

Using Nyquist scripts in Audacity.

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

### Adding sounds, numbers and vectors

This is a mini-tutorial about how Nyquist adds sounds, numbers and vectors.

The examples may be run in the Nyquist Prompt effect, and use "version 4" LISP syntax (in Audacity 2.1.2/2.1.3 ensure that "Use legacy (version 3) syntax" is NOT enabled). Note that in the code examples, text that occurs after a semicolon is treated by Nyquist as a "comment" and is ignored. Comments are provided to help people reading the code to understand what the code means.

There are three similar functions in Nyquist for adding things: "+", "SUM", "SIM"

The first is a straightforward arithmetic "add" function that adds a list of numbers. It can operate on integers (whole numbers) or "floats" ("floating point" numbers)
Code: Select all
`(+ 5 3)  ;returns 8`

Code: Select all
`(print (+ 5 3))  ;same as the first example, but explicitly "prints" the integer number "8"`

Code: Select all
`(print (+ 0.5 1 2 3))  ;prints the floating point number "6.5"`

The function "SIM" is identical to the function "SUM". For clarity, "SIM" is more often used when referring to sounds, and "SUM" used when referring to numbers, but as they are functionally identical they are interchangeable. In the following examples I shall use "SUM", but "SIM" could be used and there would be no difference.

"SUM" (and "SIM") may be used to add numbers, returning the same result as "+", but is slightly slower because the underlying code is more complex.
Code: Select all
`(sum 5 3)  ;returns 8`

Code: Select all
`(print (sum 5 3))  ;prints the number "8"`

Code: Select all
`(print (sum 0.5 1 2 3))  ;prints the number "6.5"`

Working with Sounds:

The "SUM" function can also work with "sounds". Nyquist treats mono sounds as a distinct data type. With "SUM" we can add a numeric value to a sound, which adds the numeric value to the value (amplitude) of each individual audio sample. To demonstrate this, we can use the special word *TRACK*.

The word *TRACK* is itself a special kind of symbol called a "variable" that represents data. Nyquist treats the word *TRACK* to represent the audio that is selected in the Audacity track (note that the asterisks are part of this special name). The audio may be mono or stereo. If it is a mono track (one audio channel), then *TRACK* will be a "sound". If the track is stereo, then *TRACK* will be an array containing two "sounds".

In this example we will add the numeric value "0.3" to a mono sound from a selection in a mono audio track:

Selection in mono track
firsttrack005.png (11.64 KiB) Viewed 224 times

Now apply this code to the selected audio:
Code: Select all
`(sum 0.3 *track*)`

and the result is:

firsttrack006.png (11.72 KiB) Viewed 224 times

As we can see, the selected waveform has been offset by +0.3. In other words, +0.3 has been added to each sample in the selection.

The "SUM" function is also able to add sounds together. Adding sounds is also known as "mixing". In the simple case of adding sounds that have the same sample rate, addition is performed by adding the value (amplitude) of each individual audio sample from one sound, with the corresponding sample of the other sound.

In this example, we will introduce another function, "MULT", which as the name suggests provides a multiplication function that works with sounds. "Multiplying" a sound by a numeric amount is commonly known as "amplifying". If we multiply a sound by 2, then each sample value is doubled (samples with negative values become twice as negative). Doubling the amplitude of each sample value doubles the overall level of the audio - in other words, the audio is amplified by a factor of 2, which is roughly equivalent to +6 dB.

Example:
Selection in mono track
firsttrack005.png (11.64 KiB) Viewed 224 times

Now apply this code to the selected audio:
Code: Select all
`(mult 2 *track*)`

As we can see, the amplitude of the selected audio has doubled:

After amplifying by 2
firsttrack007.png (11.82 KiB) Viewed 224 times

So now we can try generating some sounds and adding them together.

This first code snippet will generate a sine tone with a frequency of 440 Hz. By default the function HZOSC generates a sine tone with an amplitude of +/- 1.0 (0 dB), but we have multiplied it by 0.5, so it is "amplified" (attenuated) to a lower level of +/- 0.5. The returned audio replaces whatever was previously selected in the track

Code: Select all
`(mult 0.5 (hzosc 440))`

Generated sine tone
firsttrack008.png (10.95 KiB) Viewed 224 times

After amplifying by 2
firsttrack007.png (11.82 KiB) Viewed 224 times

and this time we will "add" the 440 Hz sine tone to the existing track audio:
Code: Select all
`(sum (mult 0.5 (hzosc 440))     *track*)`

Mix track with sine tone
firsttrack009.png (11.7 KiB) Viewed 224 times

Now we can see (and hear) that the sine tone has been mixed with (added to) the track audio.

Working with Stereo Sounds

Nyquist handles stereo as an array ("vector") containing two "sounds". The first element of the array is the left channel and the second element is the right channel. Audacity tracks are either mono or stereo, so returning audio from Nyquist must either be a mono sound, or an array containing two sounds. Unfortunately there is currently no way for Nyquist to tell Audacity to create a stereo track, so to generate stereo audio with Nyquist it is necessary to make a selection in a stereo audio track.
• If Nyquist returns an array of 2 sounds into a selected stereo track, the first sound element is the left channel and the second sound element is the right channel.
• If Nyquist returns a mono sound into a selected stereo track, the sound is written into both channels (the sound is duplicated)
• If Nyquist returns an array of 2 sounds into a selected mono track, an error is shown
• If Nyquist returns an array of one sound, or more than 2 sounds, and error is shown

To create an array of sounds, we can either declare the array first, and then add the sounds,
Example:
Code: Select all
`(setf stereo (make-array 2))(setf (aref stereo 0) (hzosc 200))(setf (aref stereo 1) (hzosc 440))`

or more usually we will use the VECTOR command that declares the array with its contents in one step
Example:
Code: Select all
`(vector (hzosc 200)  (hzosc 440)) `

Explanation:
In the first of these two examples, the MAKE-ARRAY function creates an array with 2 elements, and is assigned to the variable "stereo".
We then use the AREF command to access the first element (index 0) and set it to the sound generated by (hzosc 220), which is a 220 Hz sine tone.
The second element (index 1) is set to a 440 Hz sine tone.
Note: If we run this example as is, Nyquist returns the result of the final line, which is the 440 Hz "sound" and NOT the (stereo) array of sounds. If we wish to return the stereo array, we can do so by adding a final line with the variable "stereo"
Code: Select all
`(setf stereo (make-array 2))(setf (aref stereo 0) (hzosc 200))(setf (aref stereo 1) (hzosc 440))stereo  ;return the value of this variable`

In the second example, we create an initialized vector containing two sounds in one step. Nyquist returns the evaluation of this command, which is the stereo audio.

The command SIM (same as "SUM") is described in the Nyquist manual as (abridged):
Returns a sound which is the sum of the given behaviors evaluated with the current value of *warp*. If behaviors return multiple channel sounds, the corresponding channels are added. If the number of channels does not match, the result has as many channels as the argument with the most channels. For example, if a two-channel sound [L, R] is added to a four-channel sound [C1, C2, C3, C4], the result is [L + C1, R + C2, C3, C4]. Arguments to sim may also be numbers. If all arguments are numbers, sim is equivalent (although slower than) the LISP + function. If a number is added to a sound, snd-offset is used to add the number to each sample of the sound....

Unfortunately the manual does not explicitly state what happens when adding a number to a stereo sound, though we can deduce the behaviour by considering the multi-channel example (also worth noting that Audacity tracks currently support a maximum of 2 channels).

In the example of adding [L, R] to [C1, C2, C3, C4], the first element of the first array ("L") is added to the first element of the second array ("C1"), the second element of the first array ("R") is added to the second element of the second array ("C2"), and nothing is added to C3 or C4.
Schematically:
[L, R] + [C1, C2, C3, C4] = [L+C1, R+C2, C3, C4]

A similar thing happens if we add a mono sound to a multi-channel sound:
S + [C1, C2, C3, C4] = [S+C1, C2, C3, C4]

And similar again when adding a number:
0.3 + [C1, C2, C3, C4] = [0.3+C1, C2, C3, C4]

And we can extend this to arrays of numbers:
Code: Select all
`(print (sum (vector 1 4)            (vector 2 3 2.5)));returns the vector #(3 7 2.5)`

Example - Add a number (offset) to a stereo track:

Selection in stereo track
firsttrack010.png (11.1 KiB) Viewed 224 times

Code: Select all
`(sum 0.3 *track*)`

After adding 0.3 to stereo track
firsttrack011.png (11.22 KiB) Viewed 224 times

Example - Add a (mono) sound to a stereo track:

Selection in stereo track
firsttrack010.png (11.1 KiB) Viewed 224 times

Code: Select all
`(sum (mult 0.5 (hzosc 440)) *track*)`

After adding a (mono) sound to stereo track
firsttrack012.png (11.12 KiB) Viewed 224 times

Example - Add an array of two numbers to a stereo track

Selection in stereo track
firsttrack010.png (11.1 KiB) Viewed 224 times

Code: Select all
`(sum (vector 0.3 -0.3) *track*)`

firsttrack013.png (11.33 KiB) Viewed 224 times
steve
Senior Forum Staff

Posts: 43466
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux Debian

### Re: Adding sounds, numbers and vectors

steve wrote: ... To create an array of sounds, we can either declare the array first, and then add the sounds,
Example:
Code: Select all
`(setf stereo (make-array 2))(setf (aref stereo 0) (hzosc 200))(setf (aref stereo 1) (hzosc 440))stereo  ;return the value of this variable`

I've tried that : processing each channel separately, (below), but the result is still asymmetrical

Code: Select all
`;version 4(setq A1 (hzosc 0.090909))(setq A2 (hzosc 0.052631))(setq A3 (hzosc 0.034482))(setq A4 (hzosc 0.024390))(setq P 0.07) ; Recommend P<0.15(setf stereo (make-array 2))(setf (aref stereo 0)(clip (sim (mult A1 -0.5) (mult A2 -0.5) (mult A3 -0.5) (mult A4 -0.5) (clip (mult (sum 0.5 (feedback-delay *track* 0.00502512 (* 11 P))) A1).5) (clip (mult (sum 0.5 (feedback-delay *track* 0.01492537 (* 3 P))) A2).5) (clip (mult (sum 0.5 (feedback-delay *track* 0.02325581 (* 2 P))) A3).5) (clip (mult (sum 0.5 (feedback-delay *track* 0.03255813 (* 9 P))) A4).5)).5)))(setf (aref stereo 1) (clip (sim (mult A1 -0.5) (mult A2 -0.5) (mult A3 -0.5) (mult A4 -0.5) (clip (mult (sum 0.5 (feedback-delay *track* 0.00502512 (* 11 P))) A1).5) (clip (mult (sum 0.5 (feedback-delay *track* 0.01492537 (* 3 P))) A2).5) (clip (mult (sum 0.5 (feedback-delay *track* 0.02325581 (* 2 P))) A3).5) (clip (mult (sum 0.5 (feedback-delay *track* 0.03255813 (* 9 P))) A4).5)).5)))stereo `
Trebor

Posts: 3495
Joined: Sat Dec 27, 2008 5:22 pm
Operating System: Windows Vista

### Re: Adding sounds, numbers and vectors

Looking at the code for the left channel of "stereo" (I've rearranged the code a bit so that I can see what it's doing):
Code: Select all
`(setf stereo (make-array 2))(setf (aref stereo 0)  (clip (sim  (mult A1 -0.5)              (mult A2 -0.5)              (mult A3 -0.5)              (mult A4 -0.5)              (clip (mult A1                          (sum 0.5 (feedback-delay *track* 0.00502512 (* 11 P))))                    0.5)              (clip (mult A2                          (sum 0.5 (feedback-delay *track* 0.01492537 (* 3 P))))                    0.5)              (clip (mult A3                          (sum 0.5 (feedback-delay *track* 0.02325581 (* 2 P))))                    0.5)              (clip (mult A4                          (sum 0.5 (feedback-delay *track* 0.03255813 (* 9 P))))                    0.5))        0.5))`

You are using *track*, which is a stereo sound, so you are still adding those mono A1, A2,... terms to the left channel only. The same goes for the right channel of "stereo".

There are a couple of ways round this problem:

One is to replace each occurrence of *track* with (aref *track* 0) when calculating the left channel of "stereo", and (aref *track* 1) when calculating the right channel.

The other, and in my opinion better way, is to move the DSP code into a function. This cuts down on a lot of duplicate code. You're wanting to apply exactly the same process to both left and right channels, so we can write the code once and use it twice.

This is the code that you are wanting to apply to each channel - I've replace the variable *TRACK* with a new name "SIG" (abbreviation of "signal") because we want to processes the signal (mono sound) from the left channel, and then the signal from the right channel

Code: Select all
`(clip (sim  (mult A1 -0.5)            (mult A2 -0.5)            (mult A3 -0.5)            (mult A4 -0.5)            (clip (mult A1                        (sum 0.5 (feedback-delay sig 0.00502512 (* 11 P))))                  0.5)            (clip (mult A2                        (sum 0.5 (feedback-delay sig 0.01492537 (* 3 P))))                  0.5)            (clip (mult A3                        (sum 0.5 (feedback-delay sig 0.02325581 (* 2 P))))                  0.5)            (clip (mult A4                        (sum 0.5 (feedback-delay sig 0.03255813 (* 9 P))))                  0.5))      0.5))`

Schematically, if we represent the above code as [mycode], then we want to pass a sound "SIG" into a function, where [mycode] acts on SIG.
If we call the function DSP, then schematically we have:
Code: Select all
`(defun dsp (sig)  [mycode])`

We can then pass the left channel of *track* to the function to obtain the left channel of "stereo", and the right channel of *track* to the function to get the right channel of "stereo".
Code: Select all
`(vector (dsp (aref *track* 0))        (dsp (aref *track* 1)))`

So this is the complete code:
Code: Select all
`;version 4(setq A1 (hzosc 0.090909))(setq A2 (hzosc 0.052631))(setq A3 (hzosc 0.034482))(setq A4 (hzosc 0.024390))(setq P 0.07) ; Recommend P<0.15(setf stereo (make-array 2))(defun dsp (sig)  (clip (sim  (mult A1 -0.5)              (mult A2 -0.5)              (mult A3 -0.5)              (mult A4 -0.5)              (clip (mult A1                          (sum 0.5 (feedback-delay sig 0.00502512 (* 11 P))))                    0.5)              (clip (mult A2                          (sum 0.5 (feedback-delay sig 0.01492537 (* 3 P))))                    0.5)              (clip (mult A3                          (sum 0.5 (feedback-delay sig 0.02325581 (* 2 P))))                    0.5)              (clip (mult A4                          (sum 0.5 (feedback-delay sig 0.03255813 (* 9 P))))                    0.5))        0.5))(vector (dsp (aref *track* 0))        (dsp (aref *track* 1)))`

-------------------

There's a shorter way of writing the final "vector" statement, which is to use a pre-defined macro called "multichan-expand". You would use it like this:
Code: Select all
`;version 4(setq A1 (hzosc 0.090909))(setq A2 (hzosc 0.052631))(setq A3 (hzosc 0.034482))(setq A4 (hzosc 0.024390))(setq P 0.07) ; Recommend P<0.15(setf stereo (make-array 2))(defun dsp (sig)  (clip (sim  (mult A1 -0.5)              (mult A2 -0.5)              (mult A3 -0.5)              (mult A4 -0.5)              (clip (mult A1                          (sum 0.5 (feedback-delay sig 0.00502512 (* 11 P))))                    0.5)              (clip (mult A2                          (sum 0.5 (feedback-delay sig 0.01492537 (* 3 P))))                    0.5)              (clip (mult A3                          (sum 0.5 (feedback-delay sig 0.02325581 (* 2 P))))                    0.5)              (clip (mult A4                          (sum 0.5 (feedback-delay sig 0.03255813 (* 9 P))))                    0.5))        0.5))(multichan-expand #'dsp *track*)`
steve
Senior Forum Staff

Posts: 43466
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux Debian

### Re: Adding sounds, numbers and vectors

Thanks Steve, that's fixed the asymmetry.
( I would never have deciphered the syntax myself )
Trebor

Posts: 3495
Joined: Sat Dec 27, 2008 5:22 pm
Operating System: Windows Vista

### Re: Adding sounds, numbers and vectors

I'm getting the hang of ̶d̶i̶m̶ ̶s̶u̶m̶ ̶ sim sum.
This code provides a simulation of subtle variations in acoustic-feedback

Code: Select all
`;version 4(setq X 0.5) ; Recommend X<1(setq A1 (sum 0.5 (mult X (hzosc 0.090909))))(setq A2 (sum 0.5 (mult X (hzosc 0.052631))))(setq A3 (sum 0.5 (mult X (hzosc 0.034482))))(setq A4 (sum 0.5 (mult X (hzosc 0.024390))))(setq P 0.05) ; Recommend P<0.1(setf stereo (make-array 2))(defun dsp (sig) (mult 0.45 (lowpass4 (highpass8 (clip (sim               (mult A1 -0.5)              (mult A2 -0.5)              (mult A3 -0.5)              (mult A4 -0.5)              (clip (mult A1                          (sum 0.5 (feedback-delay sig 0.00502512 (* 6 P))))                    0.5)              (clip (mult A2                          (sum 0.5 (feedback-delay sig 0.01492537 (* 3 P))))                    0.5)              (clip (mult A3                          (sum 0.5 (feedback-delay sig 0.02325581 (* 2 P))))                    0.5)              (clip (mult A4                          (sum 0.5 (feedback-delay sig 0.003255813 (* 5 P))))                    0.5)) 0.5) 60) 6666)))(vector (sim (aref *track* 0)(dsp (aref *track* 0))) (sim (aref *track* 1)(dsp (aref *track* 1))))`

Guitar Before-After, (X=1, P=0,1).wav
Trebor

Posts: 3495
Joined: Sat Dec 27, 2008 5:22 pm
Operating System: Windows Vista