building an audio track from labels

I would like to have a nyquist plugin which would build from scratch an audio track using an audio clip or specific audio sound (for instance in a file or even preferably in clipboard) and a label track to do so.

It would read the audio clip or sound, put it in the clipboard and then create a new audio track where it would inject that clipboard sound at timestamp of every label given in the label track. At the end I would have an audio track with that same sound at every timestamp given by the labels of the label track.

But I have no idea of how to code that in nyquist, especially as far as the audacity nyquist related functions are concerned.

Would someone like to help me on that ?

Nyquist cannot read from the clipboard. Nyquist does not have access to the Windows clipboard or to Audacity’s clipboard.

Nyquist can read from a file, but it’s much easier to read from a selected audio clip.

Nyquist itself cannot create tracks. Nyquist effects receive selected audio from Audacity, process it, and return it to Audacity. Audacity then replaces the selected audio with the audio data passed to it from Nyquist.

You really need to use Audacity’s “Macro Scripting” commands to do that, and use Nyquist to drive the scripting commands.
See: Nyquist-Macros - Audacity Manual

This would be a very ambitious project for anyone that does not already have experience with both Nyquist and Macros.

There’s some information about Macros here: Macros - Audacity Manual
See how much of the task you can accomplish just with Macros.

Thanks a lot, Steve, for these clues.

I guess then I could create an empty audio track, inject a given sound at its beginning and then select it.
Then, if reading label track is possible in Nyquist, have Nyquist code follow a read loop of label track and for each label timestamp write the selected clip there, then go next till loop end.

I can probably code the structure of this code but need to know what audacity macro or function to use for reading label track and label timestamps, then writing selected audio clip.

Could you orient me on this?

OK, what I can do up to now with macros is :
having an audio track with a sound clip selected, and having a label track with a label on it somewhere, and having a target empty audio track :
I can select the target track, movetonextlabel and paste the selected clip. It gets pasted on target track at the label timestamp.
My question is now : how do I loop over the label track to repeat this action till the end of label track ?

That’s where you will need to use Nyquist.

But first, see if you can apply your Macro commands with Nyquist.

To do so, you need to specify that Nyquist is working as a “tool” type plug-in. You do that by putting this “magic comment” at the top of your Nyquist script:

;type tool

Then, to send the Macro command, you use “AUD-DO” followed by the command. The command must be in quotes.
Example, to add a new mono track:

(aud-do  "NewMonoTrack:")

At the end of the script, you need to return a valid value. While testing, the easiest way is to return a string (text) value that must be quoted.

Complete example that you can run in the Nyquist Prompt (Nyquist Prompt - Audacity Manual)

;type tool

(aud-do  "NewMonoTrack:")

Note that if the Macro command has quote character in it, you must escape those quotes so that they get passed to Audacity.
For example, to add a new track, select 30 seconds, and generate a 440 Hz “Square, no alias” tone amplitude 0.5:

;type tool

(aud-do "NewMonoTrack:")
(aud-do "SelectTime: Start=0 End=30")
(aud-do "Tone: Frequency=440 Amplitude=0.5 Waveform=\"Square, no alias\"")

Have a go with your Macro commands, and ensure that they all work as expected.
This reference page may be useful: Scripting Reference - Audacity Manual

Thank you again, Steve, for your help. Using AUD-DO with my macros seems to work.
Now when I launch my Nyquist code driving those macros, the original setup in my audacity session is :

  1. an audio track containing my source sound
  2. a label track containing the timestamps where I want that sound to be duplicated
  3. an empty target audio track
  4. I manually copy in the clipboard the sound to be duplicated
  5. then I select the target audio track

I called my macro PasteToNextLabel and it just activates 2 audacity macros : MoveToNextLabel and Paste

What I need now is a Nyquist loop which would repeat my macro a number of times corresponding to the number of labels. But I don’t know how to get that number from my label track. Is there a track property which would give me that number ?

Unfortunately not. You have to get that from “GetInfo:” (try it in “Extra menu > Scriptables II > Get Info”)

When run, GetInfo: returns a string (text), which is rather difficult to work with in Nyquist (Nyquist is designed to work with sounds and MIDI rather than text). Fortunately there’s an alternative version available for Nyquist that returns a “list”, which is much easier to work with.

I’m sure I’ve already posted a worked-through example somewhere, I’ll see if I can find it.

Please post the Nyquist snippets that you have so far. We can work from them to build up the new plug-in.
Use the “</>” code button to create “code tags” around your code, like this:

[c> o> de]Your code goes here…[> /> code]

Topic moved to the Nyquist / Macros board.

I thought I’d posted more than this, but the command to use is AUD-GET-INFO.
See: Nyquist-Macros - Audacity Manual

I’ll keep looking, but if I don’t find anything we can work through it here.

Yes, I had: How to specify a list of timepoints for a plugin to operate on? - #16 by steve

First off, try running this in the Nyquist Prompt when there is one or more label tracks in the project so you can see what the data is like:

;type tool
;debugflags trace

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

The second line (“debugflags”) is a “magic comment” that tells Audacity to automatically display the debug output if there is any.

The final line prints the “list of lists” that is returned by AUD-GET-INFO. Because it is a list rather than a string, it does not print to a normal message window, which is why we look in the debug window.

Again, thanks a lot, Steve. I appreciate so much your care and help for the people !
So here is my code so far :

;type tool
;control monoORstereo "Audio track type" choice "Mono,Stereo" 0

;;(aud-do "PasteSelectionToNextLabel:")
(aud-do "ImportAudio:") ; audio track containing source sound
(aud-do "ImportLabels:") ; label track containing timestamps where to paste source sound
(if (= monoORstereo 0) ; source sounds target audio track
  (aud-do "NewMonoTrack:")
  (aud-do "NewStereoTrack:"))
; Here whould come the loop for pasting sound as many times as there are labels
;(aud-do "MoveToNextLabel:")
;(aud-do "Paste:")

I tested your code with the debug window and it works all right. I’ll try to make a loop and see what comes out.

A simplified version that you might want to try first is to use “DOTIMES”.
There are some examples in the XLISP manual: XLISP dotimes

For the real thing you may be better to use “DO” rather than “DOTIMES”, but the syntax is a bit more tricky, so I’d suggest that you test with DOTIMES first.

(Tip: The index for the XLISP manual: XLISP Language Reference)

OK, so here is what I finally come to :

;nyquist plug-in
;version 1
;type tool
;debugflags trace
;name "PasteSelectionToLabels"
;control monoORstereo "Audio track type" choice "Mono,Stereo" 0
;info "Paste a copied sound from an imported audio track into an new audio track, at each label start time given by a user selected label track"

; 1. An audio track containing the source sound must be first imported manually
; 2. User must copy to clipboard the source sound manually
; 3. User selects the audio track (to avoid Audacity creating another empty useless audio track)
; 3. Label track which will be chosen must have label intervals >= source sound width (i.e. no overlapping)

; Import label track whose label timestamps will drive the source sound pasting
(aud-do "ImportLabels:")

; Create new target audio track
(if (= monoORstereo 0)
  (aud-do "NewMonoTrack:")
  (aud-do "NewStereoTrack:"))

; Loop for pasting sound as many times as there are labels
; version # 1, with dolist loop : OK
;(let ((first-track-labels (second (first (aud-get-info "Labels")))))
;  (format nil "Label track len : ~a\r" (length first-track-labels))
;  (dolist (label first-track-labels)
;    (aud-do "MoveToNextLabel:")
;    (aud-do "Paste:")
;  )
;  "Done"

; version # 2, with dotimes loop : almost OK
; N.B.: this version does not paste the last label sound correctly...
;(let ((first-track-labels (second (first (aud-get-info "Labels")))))
;  (dotimes (i (length first-track-labels) "Done")
;    (aud-do "MoveToNextLabel:")
;    (aud-do "Paste:")
;  )

; version # 3, with do loop : OK
(let ((first-track-labels (second (first (aud-get-info "Labels")))))
  (setq maxi (length first-track-labels))
  (do ((i 0 (setq i (1+ i))))
    ((eql i maxi) "Done")
      (aud-do "MoveToNextLabel:")
      (aud-do "Paste:")))

I would have included prerequisites steps 1 to 3 in the code if there were a way to ask user to select and copy the source sound and wait for its OK before continuing Nyquist code flow… Is there such an interaction possibility ?

For this code, the project must have:

  1. A label track with labels
  2. The audio to be pasted in an audio track

To run the script:

  1. Click on the audio track that contains the audio to be copied
  2. Run the script
;nyquist plug-in
;version 4
;type tool
;debugflags trace
;name "PasteAudioToLabels"

(defun copy (id start end)
  (when (<= end start)
    (return nil))
  (let (command)
    (setf command
        (format nil "SelectTracks: Track=~a TrackCount=1 Mode=\"Set\""
    (aud-do command)
    (setf command
        (format nil "SelectTime: Start=~a End=~a" start end))
    (aud-do command)
    (aud-do "Copy:")

(defun get-audio ()
;;; Copy audio in first selected audio track and
;;; return number of channels, or NIL (error).
  (let ((tracks (aud-get-info "tracks"))
        id chan sel start end)
    (setf id 0)
    (dolist (track tracks)
      (setf chan (assoc 'channels track))
      (setf sel (assoc 'selected track))
      (when (and sel chan (= (second sel) 1))
        ;; Copy audio from selected track
        (setf start (second (assoc 'start track)))
        (setf end (second (assoc 'end track)))
        (if (copy id start end)
            (return-from get-audio (second chan))
            (return-from get-audio)))
        (incf id))))

(defun add-track (chan)
  (if (= chan 1)
      (aud-do "NewMonoTrack:")
      (aud-do "NewStereoTrack:"))
  (aud-do "SelectTime: Start=0 End=0 RelativeTo=ProjectStart"))

(defun paste ()
  (let ((labels (second (first (aud-get-info "Labels")))))
    (dotimes (i (length labels))
      (aud-do "MoveToNextLabel:")
      (aud-do "Paste:"))))

(let ((chan (get-audio)))
  (cond (chan (add-track chan)
        (t    "Error.\nFailed to copy audio")))

It should automatically select the audio in the selected track, copy it, then create a new track, then paste the copied audio at each label position.

Perfect ! Works like a charm! Thanks a lot.
I guess could even add ImportAudio and ImportLabels ahead of the code to let user choose audio and labels source tracks.
Thanks again for your care and time, Steve.

P.S. By the way, does this user interaction dialog exist in Nyquist which could be put in-between two lines of code ?

I’m not sure what you mean. Can you give an example?

I have found a limitation of the paste code: If the first label is at time = 0, then the clips get pasted in the wrong places. This is because the initial “MoveToNextLabel”, which should go to the first label, goes to the second label instead.

To fix that, you would need to check if the first label is at time = 0, and if it is, skip the first “MoveToNextLabel”

If you’ve installed the plug-in, you could use this Macro (a “normal” macro Macros - Audacity Manual)

SelectTracks:Mode="Set" Track="0" TrackCount="1"
SelectTime:End="0" RelativeTo="Project" Start="0"
Pasteaudiotolabels:Use_Preset="<Factory Defaults>"

Excellent idea for the macros.

As far as user interaction is concerned, I was thinking of fetching the source sound out of an existing audio file which would normally contain much more than the sound I am interested in. For instance, I could look for a conga specific sound out of a conga drum more complex part already containing a lot of various sounds. So I would like to load the conga audio file, and the label file as well, and once done let the user select a specific sound, copy it to the clipboard. The rest of the process would wait for this interaction to be over before going ahead and building the new audio track.

Let me know if I am not clear enough.

Sounds like you would just need to leave out the part of the Nyquist code that makes the audio selection.
Basically, the only parts of the “get-audio” function that you need are:

  1. Find the number of channels in the selected track
  2. Call the “Copy” function.

and in the “copy” function you don’t need to do any selecting, as the user has already done that.

Yes, I think so.

But I guess then that the ImportAudio macro and letting the user select and copy his source sound could not be part of the global macro or Nyquist code, unless dialog with the user can be part of it. That was my questioning.

That’s correct. You can’t “pause” a macro while the user does something manually, other than interacting with dialog screens such as the file import dialog.