Label data to text file

Following on from a request here.

This topic is about how to modify a plug-in that outputs labels, so that it also outputs a text file.

Writing to a text file can be quite problematic for it to work reliably, with error checking, with cross platform support, so we will take a short cut and limit the export folder to one “safe” location - the user’s “home” folder.

To test small parts of code we can use the “Nyquist Prompt” effect.

For making and testing the plug-in, we will have the plug-in open in a text editor (such as Notepad++ on Windows) while we work. Changes can then be made and tested instantly in Audacity.

The example plug-in in this forum thread will be based on the SoundFinder plug-in.
For the sake of being able to run this plug-in in a Chain, the “type” of plug-in will need to be change from an Analyze plug-in to a “Process” plug-in,
Analyze plug-ins appear in the Audacity Analyze menu.
Process plug-ins appear in the Audacity Effect menu.

From Audacity 2.0.1 (currently in alpha, but due for release soon) Nyquist “process” plug-ins may be used in Chains http://manual.audacityteam.org/manual/help/manual/man/batch_processing.html


Making Sound Finder into a “process” type effect.
Make a copy of SoundFinder.ny and rename it SoundFinderText.ny
Save the file in the Audacity Plug-ins folder.

Open SoundFinderText.ny in a text editor and change line 3 from

;type analyze

to:

;type process

Change line 5 from:

;name "Sound Finder..."

to:

;name "Sound Finder to Text File..."

Restart Audacity and there should be a new effect (below the line) in the Effect menu called “Sound Finder to Text File…”

The “home” folder (Home directory).
This is the root of a users “user space”. The exact location depends on the operating system, but in Nyquist it can be found using the following code (run in the Nyquist Prompt
effect)

;;; home directory
(defun home ()
  (or (get-env "HOME")            ; Mac / Linux
      (get-env "UserProfile")))   ; Windows

(print (home))

To run the Nyquist Prompt effect, part of an audio track must be selected.

Explanation:
Line 1: This is a comment. Any line beginning with one or more semicolons is ignored by Nyquist - it is treated as a “comment”.

Line 2: We are defining a function. The name of the function is “home”
Parameters (arguments) can be passed to a function. If we want to pass arguments to a function we would list the names of those parameters in brackets after the name of the function. In this case we are not passing any arguments, so the brackets are empty.

Lines 3 and 4: These lines read environmental variables from the operating system.
Linux and Mac have an environmental variable called “HOME”.
Windows has an environmental variable called “UserProfile”.

Lines 1 to 4: The function will look for an environmental variable called “HOME” and if that does not exist, for an environmental variable called “UserProfile”. The value of the environmental variable is the path to the users “home” directory.

Line 6 calls the function (home) and prints the value.

Writing a file to the home folder. (part 1)

First we create a fully qualified file name for the output file which will be written in the “home” folder.

;;; home directory
(defun home ()
  (or (get-env "HOME")            ; Mac / Linux
      (get-env "UserProfile")))   ; Windows

; file separator - the right sort of "slash" as a string.
(setq slash (format nil "~a" *file-separator*)) 

; remove the file separator from the end of the path
; if present - works around some Windows weirdness.
(setq path (string-right-trim slash (home)))

(setq name "output.txt") ; a file name

; put the path and file name together into 
; one string and return to Audacity. 
(format nil "~a~a~a" path slash name)

Try this in the Nyquist Prompt - what output do you get?

I will be able to try this in about an hour, I will keep you posted. Thank you so much for the help

Looking at the soundfinder.ny code, it’s a bit of a mess, so I’ve just tidied it up a bit so that we can see what we’re doing.

I’ve changed the name of the variable “l” to “labels-list”.
It’s not generally a good idea to use single letters as variable names, particularly not a letter that can be easily mistaken for some other character. Much better to use a descriptive name.

I’ve also changed the “type” to “process” so that we can use it in a Chain.
The name of the effect is “Sound Finder to Text File…”

In order to add a file name, we can use a Text Input Widget.
As it says in that document, the Text Input Widget was introduced with “version 2” plug-ins, so we need to also change the plug-in version number to “version 2” (line 2).
The new code for adding a control for entering a file name is on line 13:

;control name "File name" string "" "Output.txt"

Note that the “initial-string” is set to “Output.txt”, so that is our default output file name.


We will also need the function for finding the home directory, so I’ve added that in at lines 34 to 37:

;;; home directory
(defun home ()
  (or (get-env "HOME")            ; Mac / Linux
      (get-env "UserProfile")))   ; Windows

Download this file and replace your current SoundFinderText.ny file with it.
SoundFinderText.ny (5.56 KB)
You should notice that the “Sound Finder to Text File…” effect now has a text input field for the file name (and much of the excess verbosity removed from the interface).
Other than that it should behave exactly the same as the usual SoundFinder effect.
Check that it does.

So far so good, it seems to be working for me, although the Nyquist Prompt Comes up with a blank screen and just the cursor blinking and an OK button to press. Where is Output.txt written to?

And also, once we have all of the times is it possible to find an average time of the even numbered labels as well as an average time of the odd numbered labels?

So far there isn’t a text file. That’s what we are going to add.
Is the effect creating a label track and marking the sounds like sound finder does?
Have you used Sound Finder before?

Writing a text file (part 2)

The code in Writing a file to the home folder. (part 1) should have produced a valid, fully qualified file name (full path and file name).
Did it? What did it produce?

This must be working before we can add to it.

We can then use the (open) function to open a file for writing.
http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-190.htm

Add this code after the code from part 1.

(setq outputfile (format nil "~a~a~a" path slash name))
(setq fp (open outputfile :direction :output))
(format fp "Hello World")
(close fp)
(format nil "The output should be here:~%~a" outputfile)

What do you get?

It’s possible to do just about anything, but first things first.
Let’s have a look at the data that we currently have.

Have a read of this bit about “Return Values” Missing features - Audacity Support
Q. What data do we expect from the variable “labels-list”?

Try changing the last line of SoundFinderText.ny from:

labels-list

to

(format t "~a" labels-list)

then run the plug-in but use the Debug button rather than the OK button.
What do you get?

When I run the SoundFinderText and hit Debug instead of OK, I get a message saying that Nyquist did not return any audio, and then another window with Nyquist Output that in this case said ((-0.09 1.01 1))

What does this mean? It is not producing labels either.

There is however a file in my directory called output.txt now that says “Hello World” I am assuming this is where the data will be sent to ? can we move to that step now?

When I run the Effect Sound Finder to Text and press OK, it says that Nyquist did not return any audio, and the output.txt is created.
When I run the Effect Sound Finder to Text and press Debug, it says that Nyquist did not return any audio, the output.txt is created, and I get the error message ((-0.09 30.01 1)) Is this an interval of time?

Good, that’s exactly what should happen.

Did you read the link?

Return Values

The result of the last computation within the plug-in code will be given back from Nyquist to Audacity. According to the data type of the Nyquist return value one of the following actions will be invoked in Audacity:

Sound: > The sound will be re-inserted into the selected part of the Audacity track. If the returned sound is shorter or longer than the original sound, the selection will be reduced or augmented. If a mono sound is returned to a stereo track, so in both channels of the stereo track the same mono sound will be inserted. If a stereo sound is returned to a mono track, an error message will be displayed.

String: > A dialog window will appear with the string being displayed as text.

Number: > A dialog window will appear with the number being displayed as text.

List: > If a specially formatted list is given back to Audacity, a label track will be created below the audio track(s).

For point labels the format is:

((number “string”) (number “string”) … )

>
> The list to create a label track must contain one or more lists, each of which must have:
>
> ```text
number - (an integer or float) is the time in seconds from the beginning of the Audacity selection, where the label will appear.
"string" - a string to be displayed in the label's text field.

For region labels (Audacity 1.3) each label list must contain two int-or-float elements, one for the start and one for the end of the label region.

((number number “string”) (number number “string”) … )

>

"labels-list" is a variable, a symbol, that holds a special list of lists.
What we did was to print out the list to the debug screen rather than sending it back to Audacity, where Audacity would then recognise the special list format and make a label track from it.

When you ran the code, you had a list, containing one list, containing the numbers -0.09 1.01 1.
Printing out a list displays the list items in brackets, so

```text
(-0.09 1.01 1)

is one list containing the numbers -0.09 1.01 1.

((-0.09 1.01 1))

Is a list, containing one list item and that list item is a list of numbers.

To create a list we can use the function (list)
So, for example to create a list of “string values” we could write:

(list "apples" "oranges" "bananas" "grapes")

To create a list of numbers we could write:

(list 1 2 3 4 5)

To create a list of lists we could write:

(list (list "apples" "oranges" "bananas" "grapes")(list 1 2 3 4 5))

Region labels have the format described above, so we can create some labels in Audacity with the following code in the Nyquist Prompt.

(list
  (list 2 3 "apples")
  (list 4.5 6 "orranges")
  (list 9 10.001 "bananas"))

Try making some labels yourself.

Also, make a good test sample to run the SoundFinderText that will create labels at specific known positions, and see how the numbers in “label-list” relate to the labels.
labels.png

Would it be possible for you to post your SoundFinderText.ny file? I may have done something to mine it is not working properly again and I want to try the labels. What comes next? How do we route the lengths of the labels to an output file?

So what we have achieved so far:

  • We have made SoundFinder into an “Effect” [;type process] so that we can run it in a Chain (in Audacity 2.0.1 or later).
  • We have created a valid, fully qualified file name.
  • We have accessed the label data and displayed it as text in the debug screen.
    We did this using the (format) function: XLISP format


The ‘format’ function prints the specified expressions [if any] to the specified ‘destination’ using the ‘format’ string to control the print format

(format T “some text string”) will in effect “print” the data “some text string” to the standard-output, which is the Debug screen.
The crucial part here is the “T” which is a special symbol in LISP/Nyquist that represents “true”.

If we replace the T with NIL then rather than “printing” the text string, it is “returned” to Audacity. As we know:

Return Values

String: > A dialog window will appear with the string being displayed as text.

See what happens if we enter this in the Nyquist prompt:

(format nil "some text string")

It displays a message to the screen

Got it, its working. Now for the times?

When we are able to find the time of each audio piece, would I be able to find the time from the beginning of one to the beginning of another, in addition to finding the length of each individual piece?

In other words, I need to know the length from the start of Apples to the start of Oranges, and the length of Oranges.

You can download it again from the previous post.


When we changed the last line to:

(format t "~a" labels-list)

we redirected the labels to the debug screen rather than “returning” them to Audacity.
If you now add back, on a new line at the bottom:

labels-list

so that the end of the file contains:

(format t "~a" labels-list)
labels-list

then run the plug-in with the debug button, the effect will first print the label-list to the debug window, [using (format T “some data”) ]
and then return the label-list to Audacity, whereupon Audacity will use it to create a label track.

We may as well put in some meaningful comments, so let’s make the end of the SoundFinderText.ny file like this (from line 132):

;; If no sound markers were found, return a message
;; Otherwise, if some sounds were found, also optionally 
;; place a label at the end of the file.
(if (null labels-list)
    (setq labels-list "No sounds found. Try reducing the silencenlevel and minimum silence duration.")
    (if (= finallabel 1) (add-label (/ s1-length s1-srate) (/ s1-length s1-srate) "[End]")))

; print the label-list to the debug window
(format t "~a" labels-list)

; return label list to Audacity
labels-list

The next step will be, rather than redirecting the label list to the debug window, we will create a “file pointer” and redirect it to a text file.
Before I rush on, how are you getting on so far?

I am right along side you ready to move on.