Nyquist plugin broken - need help debugging

Following Steve’s invaluable help in https://forum.audacityteam.org/t/macro-how-to-make-a-selection-relative-to-labels/51042/1 I created a plugin which I remember working just fine over the course of last year. After a long pause (most probably including an update of Audacity to 2.3.3), I tried using it again and it keeps running into the error condition of “Cursor must be before or between two labels” (last line). No need to say that of course I do have my cursor position surrounded by 2 (or more, for a test) labels.

  • Have there been any changes in Nyquist command/syntax that may have rendered my script unusable in the meantime?
  • If not, could someone please help me with hints to debugging the code. I would like to print the list of labels and the cursor position (of which it says it is not between the labels), but so far I failed at finding/applying the correct syntax for adding debug-print statements or any other way to show me some debug output.
    (BTW the comments marked with “Qu” I just took over from Steve, but don’t really understand. Any insights in understanding these lines would also be appreciated)

Here is the code:

;nyquist plug-in
;version 4
;type tool
;name "fade 'n silence"
;author "Steve Daulton & Samse26"
;release 2.3.1
;copyright "Released under terms of the GNU General Public License version 2"

; This script helps you cutting a continuous recording of multiple tracks (eg. vinyl or tape) into individual tracks.
; Prerequisite:
; ============
; - label1 set at the detected end of the signal of the previous track, i.e. at the point where the music completely
;	gets submerged by the turntable rumble/noise or tape noise (end of musical fade-out)
; - label2 set at the beginning of the signal of the next track, i.e. just before the first note or at first audible 
;	signs of a musical fade-in.
; - The cursor needs to sit 
;	anywhere between labels 1 and 2
;    or
; 	anywhere left to label1, as long as there is no (third) label left to the cursor 
;		caveat: in this case the effect will happen between labels 1 and 2.
;			Else the effect will happen between the labels to the left and right of the cursor.
; Upon execution the script does the following:
; =============================================
; - It prompts you to specify the duration of the to-be
;	fade-out of previous track 	(min 0.01s, max 2s, default: 0.2s)
;	fade-in of next track		(min 0.01s, max 2s, default: 0.2s)
;	silence between the fades	(1, 2, 3, or 4 s, default: 2s)
; - fades-out the previous track so that fade-out is done (at 0%) at label1 
;	this indeed may affect some audible sigal, so label1 should only sit where the signal is already weak anyway. 
; - fades-in the next track, so that fade-in is done (at 100%) at label2
; - replaces background noise between end and beginining of fade areas with silence of the desired duration
;	(i.o.w. it cuts out the audio of any lenth and inserts silence of specified length)
; - deletes labels 1 and 2
; - adds a new label at former positon of label1 (= begin of silence) with cursor in text field, 
;	so you can type the name of the next track right in.
; Once you're done processing all tracks of the recording you can export the individual tracks with File > Export > Export Multiple 
; according to the label list.
;
; The script will abort with errors if :
; ======================================
; - initially the cursor is placed to the right of label 2 and there is no further label to the right
; - the specified fade-in time is longer than the distance between the labels (which means that fade-in would overlap with the fade-out range)


; Define control to prompt user for fade and silence times
;;name "Set fade-out/-in times and duration of silence"			; a second ";name" line would override the script name (?!)
;control fto "Fade-out time" float "seconds" 0.2 0.01 2
;control fti "Fade-in time"  float "seconds" 0.2 0.01 2 
;control sit "Silence"  int "seconds" 2 1 4

;; define function "select", with 2 args, based on Audacity "Select:" macro
;; this one works with the default value for "RelativeTo=" -- whatever this is ...
(defun select (start end)
  (aud-do (format nil "Select:Start=~a End=~a Mode=Set" start end)))

;; define another select function "select-start", working relative to SelectionStart
;; I need this because of the unknown default for "RelativeTo=" in "select" above.
;; Here is definitely need the selection relative to the selection start, and nothing else.
(defun select-start (start end)
  (aud-do (format nil "Select:Start=~a End=~a Mode=Set RelativeTo=SelectionStart" start end)))

;; define function "get-region" with one arg
;; Qu: suspecting this creates a list with cursor positions
;; Qu: where does the value for "labels" come? Or is this a return value?
;; Qu: is "now" a predefined variable?
(defun get-region (labels)
  (do* ((j 1 (1+ j))
        (next (first (nth j labels))
              (first (nth j labels))))
       ((or (>= j (length labels))
            (>= next now))
        (list (first (nth (1- j) labels)) next))))

;; get list of (manually set) labels:
;; 1st label sits at end of to-be fade-out (i.e. signal just died)
;; 2nd label sits at end of to-be fade-in (i.e. just before signal starts)
(setf labels (second (aud-get-info "Labels")))

(setf now (get '*selection* 'start))		; Qu: get current cursor position (?)

(defconstant ld-min fti)			; define minimal distance between lables in sec

(cond
 ((>= (length labels) 2)			; if there is 2 or more labels
    (setf region (get-region labels))
    (cond
     ((not (second region))			; ERROR: if there is no region (= label) to the right of the cursor
        "Cursor is not between labels")

     ((> (- (second region)(first region)) ld-min)	; if length of region is more than ld-min sec

        (select (- (first region) fto) (first region))	; Select range of (1st label-ft) up to 1st label
        (aud-do "FadeOut:")				; Fade-out with duration of ft

        (select (- (second region) fti) (second region))	; Select range of (2nd label-ft) up to 2nd label
        (aud-do "FadeIn:")				; Fade-in with duration of ft

;	Select region from end of fade-out up to beginning of fade-in (= pos X)
        (select (first region) (- (second region) fti))
	(aud-do "Cut:")			; cut this range

	(select-start 0 sit)		; select sit seconds (desired duration of silence) from current position
	(aud-do "Copy:")		; copy audio in range (could be anything)
	(select-start 0 0)		; set cursor back to left border of selection (= pos X)
	(aud-do "Paste:")		; insert copied audio
	(aud-do "Silence:")		; mute inserted audio
	(aud-do "MoveToNextLabel:")	; jump ahead to next label to the right
	(aud-do "Delete:")		; delete this label (we don't need this any more once the cursor is there)
	(select-start (- 0 sit fti) 0)	; select a range backwards from here (= negative) with a duration of sit+fti
					; this makes sure that the silence ends just at the beginning of the fade-in,
					; no matter what values for silence and fade-in durations are selected
	(aud-do "CursSelStart:")	; set cursor to the left border of selection
	(aud-do "AddLabel:")		; add a label here, 
					; do this as the last thing so we can type the title of the next track right in
	;; now we have:
	;;	- deleted (left) label 1 while cutting the range from end of fade-out to beginning of fade-in
	;;	- deleted (right) label 2 explicitly
	;;	- inserted a new label at the original position of label 1, i.e. after fade-out = beginnnig of silence
	;;	  This is the beginning of the next track (starting with sit seconds of silence) when later 
	;;	  exporting audio, split at labels.
        )

;    ERROR: if distance between labels is too small to accommodate the fade-in range (the fade-out range is outside!)
     (t "Region between labels is less than the fade-in time required before 2nd label")
	; Qu: how could I use the value of a variable (ld-min) in a string output?
      )
)

 (t "Cursor must be before or between two labels")
)

Change line 76 from:

(setf labels (second (aud-get-info "Labels")))

to

(setf labels (second (first (aud-get-info "Labels"))))

(see: https://forum.audacityteam.org/t/macro-how-to-make-a-selection-relative-to-labels/51042/8)

OK, here goes. Please refer to the full code regarding these answers:

Line 63

;; Qu: suspecting this creates a list with cursor positions

It creates a list containing the time of the label to the left of the cursor, and the time of the label after the cursor, in the form:
(list t0 t1)


Line 64

;; Qu: where does the value for “labels” come? Or is this a return value?

It comes from the “call” to the function, which is in line 85;

(setf region (get-region labels))

The value of “labels” was created previously in line 77

(setf labels (second (first (aud-get-info "Labels"))))

Line 65

;; Qu: is “now” a predefined variable?

No. It is defined in line 79

(setf now (get '*selection* 'start))		; Qu: get current cursor position (?)

Line 79

(setf now (get 'selection 'start)) ; Qu: get current cursor position (?)

This line gets the start of the “selection”. If there is no selected region, then both the start of the “selection”, and the end of the “selection” are at the cursor position.


Line 125

> ; Qu: how could I use the value of a variable (ld-min) in a string output?

To insert a variable into a string, use the FORMAT command.
See: XLISP format

I see - I didn’t expect the answer to be hidden in the post from Dec 2018 :slight_smile:
Still I don’t quite understand how this nested statement works:

  • with first (aud-get-info “Labels”) I get the first element
  • now if I apply “second” on the result of the latter (which is only a single element), how can I extract the second element?
    Maybe I’m still confused of this LISP syntax, my (remote) programming background is rather from C/C++…

Thanks anyway for your prompt answers - it’s really a joy to post in this forum :slight_smile:

As it says in this post Macro: How to make a selection relative to labels - #8 by steve

The code in my previous post is correct for Audacity 2.3.1, but requires a slight modification to work in Audacity 2.3.0.

So let’s look at what that code does:

(aud-get-info "Labels")

This command returns a list that contains information about labels in the project.
We can view the data by running this code in the Nyquist Prompt:

;type tool
(format nil "~a" (aud-get-info "Labels"))

Here’s and example for Audacity 2.3.1 or later:

((1 ((2 2 Point label track 1) (10 12 Region label track 1))) (2 ((10 12 Region label track 2))))

Rewriting that with some new lines to make it easier to read:

((1 ((2 2 Point label track 1)
      (10 12 Region label track 1)))
 (2 ((10 12 Region label track 2))))

The first element in the list contains a list of labels for the first label track:

(1 ((2 2 Point label track 1)
     (10 12 Region label track 1)))

and the second element in the list contains a list of labels for the second label track:

(2 ((10 12 Region label track 2)))

Looking at the first label track element, we can see that it is a list of two elements.
The first is the track index (tracks are numbered starting with “0” as the top track in the project).
The second element contains the actual labels:

((2 2 Point label track 1) (10 12 Region label track 1))

In this case, there are two labels:

(2 2 Point label track 1)
; start at 2 seconds, end at 2 seconds, label text: "Point label track 1"

and

(10 12 Region label track 1)
; start at 10 seconds, end at 12 seconds, label text: "Region label track 1"

So in order to get the labels from the first label track:
The first element of (aud-get-info “Labels”) is the first label track.

(first (aud-get-info "Labels"))

The second element of the label track is the list of labels.

(second (first (aud-get-info "Labels")))

Thanks for all your comments, this helps.

Unfortunately I didn’t succeed in producing an output with the FORMAT command.
For a start I simply added a statement (format T “Hello”) after the definition of variable ld-min to my code, but I got a “unbound variable FORMAT” error (in Nyquist prompt with Debug mode). Putting the same statement preceded by just the default header statements produces an output OK.
I seem to be missing something fundamental.

BTW What is the diffrence between the (FORMAT T “hello”) and the (t “Hello”) syntax? I keep getting lost between the various criss-crossing of documentation to Nyquist/XLISP/Audacity manuals …

Following your latest post, I understood that I should rather use "FORMAT nil … " instead of “FORMAT T …” (as used in the examples in the XLISP manual). I was confused where stdout would go…
When I tried I tried your snippet to get the list of labels, I got the correct output.
But when I just changed the statement in my code to (format nil “~a” “Hello”) I still don’t get an output.

Bingo - I completely missed that there can be multiple label tacks, hence the list will not only contain labels, but also grouped by tracks!
So “first” gets me to the track and “second” gets me to the label. Perfect!

You will need to show me the code.


If you run this in the Nyquist Prompt, it returns “Hello World” as the return value, which Audacity will print in a message box:

(format nil "Hello World")

If you run this in the Nyquist Prompt, it prints “Hello World” to the Debug window. The empty string “” on the last line is so that Audacity receives a valid “return value”. Audacity ignores the empty string (a “no-op

(format t "Hello World")
""

The command being used is the “FORMAT” function.
The first command arguments (T or NIL) tells Nyquist where to send the formatted text.
The second argument is the text to print.
(See: XLISP format)

To include the value of a variable, we use a placeholder (“format directive”).
In C/C++ we have format directives such as %d, %s, %f.
In Nyquist / Lisp, we have format directives such as ~a, ~s.

Here’s an example to try in the Nyquist Prompt:

;control num "Enter a number" int-text "" 0 nil nil

;; "num" is a variable. The control above sets it's value.
(format nil "You entered: ~a" num)

Ok, I stripped down the code and added several print statements (TEST0 - 5), 4 to a message box (nil) and 2 to the debug window (t)

;nyquist plug-in
;version 4
;type tool
;name "fade 'n silence"
;author "Steve Daulton & Samse26"
;release 2.3.1
;copyright "Released under terms of the GNU General Public License version 2"

; Define control to prompt user for fade and silence times
;;name "Set fade-out/-in times and duration of silence"
;control fto "Fade-out time" float "seconds" 0.2 0.01 2
;control fti "Fade-in time"  float "seconds" 0.2 0.01 2 
;control sit "Silence"  int "seconds" 2 1 4

;; TEST0 output to message box ==========================
(format nil "~a" "0) hello")

(defun select (start end)
  (aud-do (format nil "Select:Start=~a End=~a Mode=Set" start end)))

(defun select-start (start end)
  (aud-do (format nil "Select:Start=~a End=~a Mode=Set RelativeTo=SelectionStart" start end)))
;; TEST1 output to message box ==========================
(format nil "1) Select:Start=~a End=~a Mode=Set RelativeTo=SelectionStart" "blabla" sit)

(setf labels (second (first(aud-get-info "Labels"))))
;; TEST2 output to message box ==========================
(format nil "2.1) ~a" (aud-get-info "Labels"))
(format nil "2.2) ~a" labels)

(setf now (get '*selection* 'start))
;; TEST3 output to debug window ==========================
(format t "3.1) ~a" now)
""
(format t "3.2) ~a" *selection*)
""

(defconstant ld-min fti)

; TEST4 output to debug window =========================
(format t "4) Hello t")
""
;; TEST5 output to message box ==========================
(format nil  "5) ld-min = ~a sec" ld-min)

When I run this at the Nyquist prompt with “OK” I don’t get a single output.
When I run this with Debug (also “Debug” in the following control), I get

3.1) 8.62286error: unbound variable - SELECTION
if continued: try evaluating symbol again
1> “”
1> 0.2
1> 4) Hello tNIL
1> “”
1> “5) ld-min = 0.2 sec”
1>

I have two labels at 8 and 9 sec, and a cursor at 8.623.
So I seem to get “now” OK, and 4) and 5) - but all somehow garbled…

Printing to the debug output with “format t” can be done at any time, and as long as the script reaches the command, the debug message will be printed to the debug output.

Printing to an Audacity message box (with “format nil”) is a different matter. Audacity only receives ONE “return value”. The “return value” is the final result that is passed back from Nyquist to Audacity. If the return value is a “string” (text), then Audacity will display it in a message box.
See: https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference#Return_Values

You have errors here:

(setf now (get '*selection* 'start))
;; TEST3 output to debug window ==========================
(format t "3.1) ~a" now)
""
(format t "3.2) ~a" *selection*)
""

Firstly, the double quote (empty strings) do nothing in this context.
When I used them in an earlier example, it was as the final statement in the script so that the empty string would be the “return value”. In this part of your script, they are not returned to Audacity and simply do nothing. For clarity, they should be removed.

This line should work, because the variable “now” was given a value.

(format t "3.1) ~a" now)

This line won’t work, because “selection” doesn’t have a value (it is a “symbol” but it is not bound to a value).

(format t "3.2) ~a" *selection*)

That’s the line that creates the error in the debug output:

error: unbound variable - *SELECTION*

Audacity only receives ONE “return value”.

Hmm … in other words: you can only have a single message box output from a script - am I right?
Wouldn’t this mean that all “format nil” statements in my snippet get ignored, except for the last one in line? Then I would expect to get a message box for (format nil “5) ld-min = ~a sec” ld-min) - however I got it as an output in the debug window.
Seems all a bit too confusing to me …

the double quote (empty strings) do nothing in this context.

Understood - will remove them.

selection” doesn’t have a value (it is a “symbol” but it is not bound to a value)

guess I need to do more reading to understand the different types of identifiers …

Yes.
Running Nyquist in Audacity will return one printable message, or labels, or sound. There is always only one “return value”.


Yes.


The code won’t get to the last line because of the errors earlier on.

If the code reached the last line, and ld-min has a printable value, then the final line would print.
For example, if you run this:

(setf  ld-min 123)
(format nil  "5) ld-min = ~a sec" ld-min)

It will print:

  1. ld-min = 123 sec