searching the "Edit labels" table - frequent use case

Suppose you’ve got a problem area in your audio.
Want to find a replacement.
Do Select/Store cursor position
Do Edit/Labels/Edit labels. Luckily you have labeled many positions of good quality segments.
Do ==> Search for a string to find the label marking the ‘good’ place. <= No can do currently.
Oh well, just scroll up and down, see if you can find it the hard way.
Do OK. The Label table vanishes.
Do spacebar. The cursor goes to the label you have picked. NICE FEATURE! And, the Labeled audio is played.
Select & copy the audio you want there.
Do Select/Cursor to stored cursor position. Adjust selection.
Paste as desired.
WONDERFUL! … except you can’t do the search. Not implemented.

But, you say, just Export the labels, and search in the new “Label track.txt” text file.
Yes, and then what?
You could copy the time stamp from that row, but then you can’t AFAIK paste
a time stamp into Audacity.

Searching for a string right in the “Edit labels” table would seem low-hanging fruit.
It would help me out a lot. Also happy to help with coding if necessary.
Thanks for your consideration.

I doubt that this is a problem for the majority of users as it is only really a problem for anyone that has a project with a vast number of named labels, but I do see the benefit of this feature request for those cases where a project does have a vast number of named labels.

One possible solution would be to write a Nyquist Macro (https://manual.audacityteam.org/man/nyquist_macros.html) to search for text in the labels and select the region / cursor position of the first label that contains the search string.

For exact matches only (where the full label text must be matched), this code should work:

;type tool

;control searchstring "Label to find" string "" ""

(defun find (str)
  (let ((labeltracks (aud-get-info "Labels")))
    (dolist (trk labeltracks nil)
      (setf labels (second trk))
      (dolist (label labels)
        (when (string-equal (third label) str)
          (return-from find (list (first label) (second label))))))))
          

(setf match (find searchstring))
(if match
    (aud-do-command "SelectTime" :start (first match) :end (second match))
    (format nil "~s not found in labels" searchstring))

To run the above code, copy and paste it into the “Nyquist Prompt” effect. (See: https://manual.audacityteam.org/man/nyquist_prompt.html)

The code could easily be converted to an installable plug-in by adding a few “headers” to the text, and saving it as a plain text file with the file extension “.ny” (See: https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference).

This version supports partial matches. That is, if the search string is in the label, then a match is found.
Example:
Searching for “and” will be found in “Cat and Dog”.

It also has an option for case sensitive or case insensitive searching.

;type tool

;control searchstring "Find in labels" string "" ""
;control casesensitive "Capitalization" choice "Case Insensitive,Case Sensitive" 0

(defun find (str)
  (let ((labeltracks (aud-get-info "Labels")))
    (dolist (trk labeltracks nil)
      (dolist (label (second trk))
        (when (found-in str (third label))
          (return-from find (list (first label) (second label))))))))
          

(defun found-in (needle stack)
  ;; Return true if "needle" is in "stack".
  (let ((nlen (length needle))
        (slen (length stack)))
    (when (< slen nlen)
      (return-from found-in nil))
    (dotimes (i (- (1+ slen) nlen) nil)
      (setf substr (subseq stack i (+ i nlen)))
      (if (= casesensitive 0)
          (when (string-equal needle substr)
            (return-from found-in t))
          (when (string= needle substr)
            (return-from found-in t))))))


(setf match (find searchstring))
(if match
    (progn
      (aud-do-command "SelectTime" :start (first match) :end (second match))
      "")
    (format nil "~s not found in labels" searchstring))

Thanks, Steve. This works nicely on 3.1.3. I keep some practice “recording” sessions squirreled away in .aup3 files (original in .WAV, annotated in .aup3). I can export the labels, and Windows can search the text files easily, which locates the correct .aup3 file(s) for me; the problem is then finding the label within a “forest” of annotations. This “plug-in” can help speed this search up for me. :wink:

One tiny issue. I am currently trying to do much of my work in 3.2 Alpha in hopes of spotting glitches that affect me :wink: before the real release.

When I run your program on 3.2 Alpha I get the following “Exception code 0xc00000005” crash in Audacity:

Operating system: Windows NT
                  10.0.19044 
CPU: x86
     GenuineIntel family 6 model 142 stepping 9
     4 CPUs

GPU: UNKNOWN

Crash reason:  EXCEPTION_ACCESS_VIOLATION_READ
Crash address: 0x1300002b
Process uptime: 33 seconds

Thanks for the feedback jademan. I’ll check it out in 3.2.0 alpha.

Hmm… Looks like a bug in Audacity 3.2.0 alpha. In the debug build I’m seeing:

ASSERT INFO:
../source_subfolder/src/common/wincmn.cpp(1492): assert ""firstHandler->GetPreviousHandler() == __null"" failed in PopEventHandler(): the first handler of the wxWindow stack should have no previous handlers set

BACKTRACE:
[1] wxWindowBase::PopEventHandler(bool)
[2] DefaultEffectUIValidator::~DefaultEffectUIValidator()
[3] DefaultEffectUIValidator::~DefaultEffectUIValidator()
[4] std::default_delete<EffectUIValidator>::operator()(EffectUIValidator*) const
[5] std::unique_ptr<EffectUIValidator, std::default_delete<EffectUIValidator> >::~unique_ptr()
[6] EffectUIHost::~EffectUIHost()
[7] EffectUIHost::~EffectUIHost()
[8] wxAppConsoleBase::DeletePendingObjects()
[9] wxAppConsoleBase::ProcessIdle()
[10] wxAppBase::ProcessIdle()
[11] wxApp::DoIdle()
[12] g_main_context_dispatch
[13] g_main_loop_run
[14] gtk_main
[15] wxGUIEventLoop::DoRun()
[16] wxEventLoopBase::Run()
[17] wxAppConsoleBase::MainLoop()
[18] wxAppConsoleBase::OnRun()
[19] wxAppBase::OnRun()
[20] AudacityApp::OnRun()
[21] wxEntry(int&, wchar_t**)
[22] wxEntry(int&, char**)
[23] main
[24] __libc_start_main
[25] _start

Yes, it’s a bug in Audacity 3.2.0 alpha. It affects any code run in the Nyquist Prompt that uses GUI controls.
Here’s a minimal example:

;control dummy "Dummy control" int "" 5 0 10
(noise)

Logged here: Crash when running Nyquist Prompt with code that has widgets · Issue #3345 · audacity/audacity · GitHub

I’ve uploaded an installable plug-in version to my blog here: https://audionyq.com/find-label-plug-in/
Fortunately this still seems to work in Audacity 3.2.0, but perhaps you (jademan) can check that it works for you too.

Yes. The installed version seems to work. :smiley:

I always used to do that in the interests of QA - BUT Dmitry carefully warned me not to do so with 3.2.0 alpha yet …

Peter.

On Linux, Audacity 3.2.0 is not sufficiently stable or reliable for proper work. Hopefully this will improve before 3.2.0 is released, but for now I’m trying the latest alpha from time to time, report some bugs, then go back to an old version of Audacity for audio work. Fortunately Audacity 2.4.2 is still available on Linux and works well.

Shhh! Don’t tell Dmitry! :wink:

Steve! I actually had a calling to use your plug-in this morning and it saved me bunches and bunches of keystrokes. :smiley:

steve this is wonderful!
For me, the exact match version fits the bill.
That way if the first match doesn’t cut it (e.g. it doesn’t sound good enough),
I can edit the label (e.g. add “not this one”)
and find the next match.

You inspire me to learn nyquist.

Alternatively, if you use case sensitive search you could change the capitalization of the first (or any) character in the label that you want to skip.

or

You could modify the plug-in to give a choice of partial match or full match.
To do that, add the line:

;control match "Match" choice "Partial string,Whole string" 0

below the other two “;control” lines, like this:

;control searchstring "Find in labels" string "" ""
;control casesensitive "Capitalization" choice "Case Insensitive,Case Sensitive" 0
;control match "Match" choice "Partial string,Whole string" 0

and modify the “found-in” function like this:

(defun found-in (needle stack)
  ;; Return false if "whole string" match fails.
  (when (= match 1)
    (if (= casesensitive 0)
        (when (string-not-equal needle stack)
          (return-from found-in nil))
        (when (string/= needle stack)
          (return-from found-in nil))))
  ;; Partial match: Return true if "needle" is in "stack".
  (let ((nlen (length needle))
        (slen (length stack)))
    (when (< slen nlen)
      (return-from found-in nil))
    (dotimes (i (- (1+ slen) nlen) nil)
      (setf substr (subseq stack i (+ i nlen)))
      (if (= casesensitive 0)
          (when (string-equal needle substr)
            (return-from found-in t))
          (when (string= needle substr)
            (return-from found-in t))))))

Note that when editing a Nyquist plug-in, it is essential that a “plain text” editor is used. For Windows I recommend NotePad++ (https://notepad-plus-plus.org/)

I like it!

The next step is to paste in some audio (fix my wrong notes!?)
but then that creates clicks at the edges.

“Click removal” effect doesn’t work for me.
The “Repair” macro works well, but it’s tedious to select the two click spots,
or four if it was done to 2 tracks,
adjust the width (not too wide!) etc. over and over and over.

So, jumped into Nyquist programming finally.
Here’s my brand new plug-in that works wonderfully after
a pasting, or changing pitch or tempo or volume.
It works on exactly the tracks already selected,
so it can fix all four click spots in one step.

After installing with “Tools/Nyquist Plug-in Installer…”
I assigned a kbd shortcut. Exhiliarating.


;nyquist plug-in
;version 4
;type tool
;name "wrap-selection-start-and-repair"
;author "Roger"

(let ( (thestart (get '*selection* 'start))
         (theend   (get '*selection* 'end))
       )

  (AUD-DO (format nil "SelectTime: Start=~S End=~S" 
    (- thestart 0.001  )
    (+ (get '*selection* 'start) 0.001 )
  ))
  (aud-do "Repair")

  (AUD-DO (format nil "SelectTime: Start=~S End=~S" 
    (- theend 0.001  )
    (+ theend 0.001 )
  ))
  (aud-do "Repair")
)