Inverting audio using Nyquist?

Hello all,

I’m working on a nyquist plugin that uses

(s-min s 0.5)

to cut off anything above the .5 mark of audio.

Now I’m trying to have an option if you want to remove the underside .5 of the audio, the plugin inverts the audio first, applies the effect, then inverts the audio again, essentially making the bottom side cut off. I’ve looked everywhere and examined different plugins, and none seem to show how to invert the audio using nyquist.

Like, what command in nyquist simply inverts the audio?

It’s probably simpler than you are expecting.

I guess that you know that (PCM) digital audio is just a series of numeric “sample” values, that represent the voltage of an audio signal at points in time:

As shown by the (default) vertical scale on the left end of the audio track, Audacity treats samples as values between +1 and -1. Dead silence is when all samples have a value of zero.

When inverting a waveform, all we are doing is to changing positive values to negative, and negative values to positive. In other words, multiply by -1 (minus one).

(mult -1 sig) ; where 'sig' is the signal being inverted

If you want to clip both the top and bottom of the waveform, use the CLIP function:

(clip sig 0.4)

Aside:
I notice that you are using the legacy syntax of “s” to represent the track audio, or at least I assume that you are using “S” to represent the track audio. Generally it’s better to use the new (version 4) syntax. The “track” keyword avoids conflicting with the Nyquist global value 0.25 (Introduction and Overview), and it avoids ambiguity. Also, most of the Audacity “property list” variable are only available when using version 4 (or later) syntax (Missing features - Audacity Support)

Well, so far the plugin looks like this now:

;nyquist plug-in
;version 4
;type process
;preview linear
;name "Diode Clipping..."
;action "Applying Diode Clipping..."
;author "8bit-coder"
;copyright "No copyright"

;control amt "Amount of clipping" real "0-1" 0.5 0 1

(clip sig amt)

Now it looks like this when loaded up into audacity:

But when I click OK to apply the effect, it spits out:

There’s also another problem, I want to be able to have a choice on whether to clip one side, or both, and also to have positive or negative side clipped as a result of one side only.

For clipping one side or both, I figured out by reading other plugins that you need to have:

;control side "Clipping type" choice "Positive Only,Negative only,Both" 0

but I can’t figure out how to take the variable side and have conditions for it to work. I am a java programmer however, but it’s still difficult.

The other problem is how to take the amt variable from:

;control amt "Amount of clipping" real "0-1" 0.5 0 1

and use it in

(clip sig amt)

and

(s-min s amt)

(the reason this old code is because I still want to have the ability to also only clip one side)

In version 4 syntax, the sound from a selected mono audio track, or an array of two sounds from a selected stereo track, is bound to the variable *TRACK*.
(Missing features - Audacity Support)

To get your “diode clipping” to work, either:

(setf sig *track*)
(clip sig amt)

or more simply, just:

(clip *track* amt)

similarly:

(s-min *track* amt)

From the control, the variable “side” will have a value of 0, 1 or 2, depending on which choice is selected.
To translate that into a control construct, there are several options (XLISP: An Object-oriented Lisp)

(if (= side 0)
    (do-something...)
    ; else
    (if (= side 1)
        (do-something-else)
        (do-third-option...)))

(the line beginning with a semicolon is a comment)

(cond
  ((= side 0) (do-something...))
  ((= side 1) (do-something-else))
  (t (do-third-option...)))

or probably the best way

(case side
  (0 (do-something...))
  (1 (do-something-else))
  (t (do-third-option...)))

Ok, now full plugin with an added “tube distortion” effect:

;nyquist plug-in
;version 4
;type process
;preview linear
;categories "http://lv2plug.in/ns/lv2core#ModulatorPlugin"
;name "Clipper..."
;action "Applying Clipper..."
;author "8bit_coder"
;copyright "No copyright"

;control amt "Length of clipping(0 is most, 1 is least)" real "0-1" 0 0 1
;control side "Clipping type" choice "Positive Only,Negative only,Both,None" 0
;control tube "Tube distortion" choice "Yes,No" 0

(case tube
	(0 (setq drive 0.2)(hp (sim (mult (- 1 drive )(s-min *track* 0))(mult (+ 1 drive)(s-max *track* 0)))10))
	(1 (s-min *track* 1)))

(case side
	(0 (s-min *track* amt))
	(1 (mult -1 sig)(s-min *track* amt)(mult -1 sig))
	(2 (clip *track* amt))
	(3 (s-min *track* 1)))

But now, the only problem is that everything works right(all options) except the negative only. What I did for the negative only, is to flip the waveform, clip it, then flip it back. But the problem is that it doesn’t do anything to the waveform. No clip, not even an error. Also, if I change

(s-min *track* amt)

to

(s-min *track* -amt)

which sounds logical, but it doesn’t do anything. Only removing the (mult -1 sig) parts will allow it to clip successfully, but not the underside, effectively the same as the positive option.

What I think is happening is that you can’t have multiple commands in a single case, yet the tube distortion case has many commands inside one condition.

Aside from code, I changed the name of the plugin to a more appropriate Clipper(Since all it does is clip waveforms).

So that’s all. All I need is to get the negative part working, then it will be a complete plugin that I will post in the plugins to test section of the forumn.

I’ve also run into another problem. As you see, I have added a rectify function(which is really just a diode) to move the negative parts to the top. There are 2 problems:

  1. The (mult -1 sig) doesn’t work just like my last post said(although putting (mult -1 track) in the nyquist prompt works, it doesn’t work for the negative clip part still!
  2. If I choose tube distortion, nothing happens, but when I switch it’s spot around, it works. Vice versa with the rectifier function. It works when the tube distortion doesn’t, but if I flip the tube distortion code above the rectifier code, the tube distortion works, but the rectifier doesn’t, no matter what options I select.

Anyways, here’s the code so far now:

;nyquist plug-in
;version 4
;type process
;preview linear
;name "Clipper..."
;action "Applying Clipper..."
;author "8bit_coder"
;copyright "No copyright"

;control amt "Length of clipping(0 is most, 1 is least)" real "0-1" 0 0 1
;control side "Clipping type" choice "Positive Only,Negative only,Both,None" 0
;control tube "Tube distortion" choice "Yes,No" 0
;control rectify "Rectification" choice "Rectify to top,rectify to bottom,None" 0

(case side
	(0 (s-min *track* amt))
	(1 (mult -1 sig)(s-min *track* amt)(mult -1 sig))
	(2 (clip *track* amt))
	(3 (s-min *track* 1)))
	
(case rectify
	(0 (s-abs *track*))
	(1 (mult -1 sig)(s-abs *track*)(mult -1 sig))
	(2 (s-min *track* 1)))

(case tube
	(0 (setq drive 0.2)(hp (sim (mult (- 1 drive )(s-min *track* 0))(mult (+ 1 drive)(s-max *track* 0)))10))
	(1 (s-min *track* 1)))

What does this do?

If the signal is “valid” (not exceeding 0dB), then track will be less than 1, so this code does nothing, so why is it there?


What is “sig” in this code?

Try running the plug-in with the debug button and the option “Negative only” and you will see the error:

error: unbound variable - SIG

That indicates that the variable “sig” has not been bound to anything - it doesn’t have a value, it’s just an undefined symbol.


No that’s not logical in Nyquist / LISP.
Try running this code in the Nyquist Prompt with the debug button:

(setf arg 4)
(print -arg)

The debug output says:

error: unbound variable - -ARG

The symbol “-arg” is NOT the inverse of “arg”. It is just another symbol, unrelated to “arg”.
To get the inverse of “arg” you could write:

(mult -1 arg)

or more simply:

(- arg)

(in the latter case, note the space between “-”, which is the function name, and “arg” which is the argument being passed to the function.)

Even then it does not really make much sense. Let’s put in a value:

(setf amt 0.5)
(s-min *track* (- amt))

What this will do, is to return the minimum of track and -0.5. Try it, and I think it will become clear.

While developing Nyquist scripts, get in the habit of using the Debug button.
If there is no debug output (empty window), then that shows that no errors (or other debug information) has occurred, which is usually a sign that everything went correctly (or nothing happened at all :confused: )

When there is an error, don’t try to understand all of the debug output, just look at the first few lines - they will often give a clue to what went wrong.

Well, now I updated the code to:

;nyquist plug-in
;version 4
;type process
;preview linear
;name "Clipper..."
;action "Applying Clipper..."
;author "8bit_coder"
;copyright "No copyright"

;control amt "Length of clipping(0 is most, 1 is least)" real "0-1" 0 0 1
;control side "Clipping type" choice "Positive Only,Negative only,Both,None" 0
;control tube "Tube distortion" choice "Yes,No" 0
;control rectify "Rectification" choice "Rectify,None" 0

(case side
   (0 (s-min *track* amt))
   (1 (mult -1 amt)(s-min *track* amt)(mult -1 amt))
   (2 (clip *track* amt)))
 
(case rectify
   (0 (s-abs *track*)))

(case tube
   (0 (setq drive 0.2)(hp (sim (mult (- 1 drive )(s-min *track* 0))(mult (+ 1 drive)(s-max *track* 0)))10)))

But I still can’t figure out why nothing works, other than the tube distortion. Even using the debug button, there is nothing. When I used each command in the nyquist window, they all worked, but now they don’t. I can’t figure out what to do at this point. I’ve even tried removing case rectify and tube, but now the side case won’t work anymore. It’s just really strange. One note is that the solution you provided for the negative amount of side, only made it clip to the negative part. That’s the problem. It clipped all the way down. What I’m trying to do is to flip the waveform, clip it, then flip it back, so it only clips the underside, not all the way from the positive to the under side. :confused:

I think you’re missing how program flow works in Nyquist / Lisp. It’s actually not much different from most other scripting languages.
(The examples below may all be run from the Nyquist Prompt effect)

The script progresses from the top of the page to the bottom of the page.

Function definitions are ignored until the function is called. When called, the function is interpreted, starting at the top and working its way down, following the directives of any flow control directives that it encounters (such as loops and conditionals).

Let’s take an example that can be run in the Nyquist Prompt.

Example 1:

(defun x (abc)
  ;; This function would return the number '42' if called
  ;; but it is not called in this script, so it is redundant (does nothing)
  42)

(defun y ()
  ;; This function returns the number 99 when called.
  99)

(setf val 1)  ; sets 'val' to 1
(setf val 2)  ; sets 'val' to 2. The previous line is now redundant
(print 3)     ; prints 3, which can be seen in the debug window
(y)           ; calls the function 'y', but we haven't done anything with the returned value
(+ 2 3)       ; returns the number 5.

;; Nyquist scripts return only the final value to Audacity. In this case, the number 5.
;; The rest of the code is valid, so there are no error messages in the debug window,
;; but the only two lines that actually do anything are the PRINT statement (which gives
;; us the number 3 in the debug window), and the last line (which returns the value 5)

So now looking at your script:

;nyquist plug-in
;version 4
;type process
;preview linear
;name "Clipper..."
;action "Applying Clipper..."
;author "8bit_coder"
;copyright "No copyright"

;control amt "Length of clipping(0 is most, 1 is least)" real "0-1" 0 0 1
;control side "Clipping type" choice "Positive Only,Negative only,Both,None" 0
;control tube "Tube distortion" choice "Yes,No" 0
;control rectify "Rectification" choice "Rectify,None" 0

(case side
   (0 (s-min *track* amt))
   (1 (mult -1 amt)(s-min *track* amt)(mult -1 amt))
   (2 (clip *track* amt)))
 
(case rectify
   (0 (s-abs *track*)))

(case tube
   (0 (setq drive 0.2)(hp (sim (mult (- 1 drive )(s-min *track* 0))(mult (+ 1 drive)(s-max *track* 0)))10)))

The value that is returned to Audacity is (only) the result from:
(reformatted for readability)

(setq drive 0.2)
(hp (sim (mult (- 1 drive )(s-min *track* 0))
         (mult (+ 1 drive)(s-max *track* 0)))
    10)

The expressions:

(case side
   (0 (s-min *track* amt))
   (1 (mult -1 amt)(s-min *track* amt)(mult -1 amt))
   (2 (clip *track* amt)))

and

(case rectify
   (0 (s-abs *track*)))

both have return values, but they are redundant because you don’t do anything with those values. They are in no way included in the value that is returned to Audacity.

If you want to return clipped audio, OR, tube distorted audio, OR, rectified audio, then you need to somehow select which one to return to Audacity.

Example 2:

;control function-choice "Choose a function to run" choice "Add,Subtract,Multiply" 0
;control val1 "First value" int "" 5 0 10
;control val2 "Second value" int "" 5 0 10

(defun my-add (x y)
  "Returns the sum of x and y."
  (+ x y))

(defun my-diff (x y)
  "Returns the difference of x and y."
  (- x y))

(defun my-mult (x y)
  "Returns the product of x and y."
  (* x y))

;; Debug: which choice was selected:
(format t "Selected function: ~a" function-choice)

;; Select which function to run, according to the
;; value of 'function-choice'.
;; The CASE special form will return the value of
;; the last expression of the matching case
;; http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-047.htm

(case function-choice
  ; When value is 0, call the function 'my-add' with arguments 'val1' and 'val2'
  (0 (my-add val1 val2))
  ; When value is 1, call the function 'my-diff' with arguments 'val1' and 'val2'
  (1 (my-diff val1 val2))
  ; Ootherwise (catch all), call the function 'my-mult' with arguments 'val1' and 'val2'
  (t (my-mult val1 val2)))

If you want to return a combination of the clipped, tube distorted and rectified audio, then you need to decide in what way you want to combine them.

Example 3:

;control function-choice "Choose a function to run" choice "X+(Y*Z),Y+(X*Z),Z+(X*Y)" 0
;control val1 "X value" int "" 1 0 10
;control val2 "Y value" int "" 2 0 10
;control val3 "Z value" int "" 3 0 10

(defun my-add (x y)
  "Returns the sum of x and y."
  (+ x y))

(defun my-mult (x y)
  "Returns the product of x and y."
  (* x y))

;; Debug: which choice was selected:
(format t "Selected function: ~a" function-choice)

;; Select how to combine the functions according to
;; the value of 'function-choice'.

(case function-choice
  (0 (my-add val1 (my-mult val2 val3)))
  (1 (my-add val2 (my-mult val1 val3)))
  (t (my-add val3 (my-mult val1 val2))))

Writing it out in longhand:

;version 4
(setf threshold 0.5)

(setf inverted (mult -1 *track*))
(setf clipped (s-min threshold inverted))
(mult -1 clipped)  ; Flip it back. This is the returned value

Writing it out all in one statement (bad style - not very readable)

;version 4
(setf threshold 0.5)
(mult -1 (s-min threshold (mult -1 *track*)))

Create a temporary local variable to hold the intermediate “inverted” value:

;version 4
(setf threshold 0.5)

(let ((inverted (mult -1 *track*)))
  (mult -1 (s-min threshold inverted)))

But better than any of the above:

;version 4
(setf threshold 0.5)

(s-max (- threshold) *track*)

Extending the idea:

Define a function to clip either positive or negative peaks

;version 4
;control threshold "Clip track at value" float "" -0.5 -1 1

(defun clip-at (sig thresh)
  "Clip either +ve or -ve peaks of SIG at value THRESH"
  (if (> thresh 0)
      (s-min sig thresh)
      (s-max sig (- thresh))))

; Call the function
(clip-at *track* threshold)

Ok, so I changed the code so it chooses which option to do:

;nyquist plug-in
;version 4
;type process
;preview linear
;name "Clipper..."
;action "Applying Clipper..."
;author "8bit_coder"
;copyright "No copyright"

;control amt "Length of clipping(0 is most, 1 is least)" real "0-1" 0 0 1
;control option "Which option to do" choice "Clipping Positive,Both,Tube Distortion,Rectification" 0

(defun clipp
  (s-min *track* amt))
  
(defun clipb
  (clip *track* amt))

(defun tubed
  (setq drive 0.2)
(hp (sim (mult (- 1 drive )(s-min *track* 0))
         (mult (+ 1 drive)(s-max *track* 0)))
    10))

(defun rectifyd
  (s-abs *track*))
   
(case option
   (0 clipp)
   (1 clipb)
   (2 tubed)
   (3 rectifyd))

but the problem now is that audacity gives the error(via using the debug button :smiley: )

error: bad formal argument list
Function: #<FSubr-DEFUN: #13331288>
Arguments:
  TUBED
  (SETQ DRIVE 0.2)
  (HP (SIM (MULT (- 1 DRIVE) (S-MIN *TRACK* 0)) (MULT (+ 1 DRIVE) (S-MAX *TRACK* 0))) 10)
1> RECTIFYD
1> error: unbound variable - CLIPP
if continued: try evaluating symbol again
Function: #<FSubr-CASE: #13331138>
Arguments:
  OPTION
  (0 CLIPP)
  (1 CLIPB)
  (2 TUBED)
  (3 RECTIFYD)
Function: #<FSubr-DEFUN: #13331288>
Arguments:
  TUBED
  (SETQ DRIVE 0.2)
  (HP (SIM (MULT (- 1 DRIVE) (S-MIN *TRACK* 0)) (MULT (+ 1 DRIVE) (S-MAX *TRACK* 0))) 10)
2> 1>

and I tried to comprehend the error, but I can’t really understand it, other than unbound variable CLIPP, which is strange. And that’s as far as I’ve gotten. Sorry for the late reply as well.

That means that there’s something wrong with the “arguments” (the “parameters”) of a function.
All functions are defined with a list of arguments, though in some cases it can be an empty list.

Example:

(+ 3 2) ; The function is "+" and there are two arguments, "3" and "2"
(+ 1 2 3 4 5) ; The same function with a list of 5 arguments
(terpri)  ; Function with a list of no arguments - prints a new line

When defining a function. the syntax is:

(defun name-of-function (list-of-arguments)
  function-body

When defining a function that tales no arguments (a list of length zero), the syntax is:

(defun name-of-function ()
  function-body



Seems to indicate a problem with “tubed”, so let’s look at that in your code:

You are defining a function called “tubed”, but where is the list of arguments? Nyquist will look at this code and think that “(setq drive 0.2)” is the list, which will confuse the heck out of Nyquist, and trigger an error.

Same thing with your other function definitions - you need to define the argument list.
More information here: XLISP defun