Get Playhead position (selection) time in seconds macro Nyquist command shortcut

Somewhere (on this forum?) I found this Nyquist script:

;version 4
;debugflags trace
(format t "~a~%" (get '*selection* 'start))
""

I use this to get the exact time of issues with audio for reporting results of proof-listening. Labels are not an option for me.

I recorded a macro:
macro.PNG
And I assigned a keyboard shortcut (T):
time.PNG
If I use the macro via shortcut or macro menu, while the playhead is in the track without selection (selection start = selection end), I get this message: “Nyquist Prompt” requires one or more tracks to be selected.

If I run the nyquist command via Tools/Nyquist Prompt, I get the desired time code, which I can CTRL-A select and CTRL-C copy:
whatiwant.PNG
I suppose this means that macros should accept selections with length = 0.

Try this as your macro:

SelectTime:End="1" RelativeTo="Selection" Start="0"
NyquistPrompt:Command=";version 4\n;debugflags trace\n(format t \"~a~%\" (get '*selection* 'start))\n\"\"" Parameters=""
CursSelStart:

Haha, perfect workaround, thanks!

I was able to assemble a h:mm:ss.mmm format output, working around the missing mod/floor functions in XLISP:

;version 4
;debugflags trace
; get selection start in ms
(setq ms (float 3902.3135))
; ms.math.floor() so we can use rem (replacement for mod) later
(setq sec (truncate ms))
; only the decimals of ms (3 of them)
(setq msonly (truncate (* (- ms sec) 1000)))

(setq hrs (truncate (/ sec 3600)))
(setq sec (- sec (* hrs 3600)))
(setq min (truncate (/ sec 60)))
(setq sec (- sec (* min 60)))

(format t "~a:" hrs)
(if (< min 10)
    (format t "0~a:" min)
    (format t "~a:" min)
)
(if (< sec 10)
    (format t "0~a." sec)
    (format t "~a." sec)
)
(format t "~a" msonly)

""

They’re not “missing”, they just have different names.
mod = rem (remainder)
floor = truncate

Nyquist also has round (not in standard XLisp).

There’s an easier / better way to format a float to a specified number of decimal places, and that’s with the FORMAT function:
https://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-121.htm
and the float-format system variable: https://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-119.htm

Example, print PI to 3 decimal places:

(print pi) ; 3.141593 ; default 6 decimal places
;; Set *float-format* to 3 decimal places:
(setf *float-format* "%.3f")
(print pi) ; 3.141593 (print does not apply format directives
(format nil "~a" pi) ; "3.142" (correctly rounded)

Well done. That works for me, except that:

; get selection start in ms
(setq ms (float 3902.3135))

You appear to be actually converting from seconds rather than ms.

For general use, it’s convenient to wrap the code in a function so that you can call it any time you like without having to duplicate code.
Here’s an example which I think will be reasonable robust. Note that the “n” argument is optional and defaults to 3 decimal places:

(defun format-time(sec &optional (n 3))
  ;; Return time formatted to hh:mm:ss + n decimal places.
  (flet ((pad (x) (if (< x 10) (format nil "0~a" x) x)))
    (let* ((hh (truncate (/ sec 3600)))
           (mm (truncate (/ sec 60)))
           (ss (- sec (* mm 60)))
           (old-format *float-format*)
           rslt)
      (setf mm (- mm (* hh 60)))
      (setf *float-format* (format nil "%.~af" n))
      (setf rslt (format nil "~a:~a:~a" hh (pad mm) (pad ss)))
      (setf *float-format* old-format)
      rslt)))


;; Test
(setq seconds (float 3902.3135))
(format nil "Time: ~a   PI: ~a" (format-time seconds) pi)

For good measure, let’s add some error checking:

(defun format-time(sec &optional (n 3))
  ;; Return time formatted to hh:mm:ss + n decimal places.
  (unless (and (numberp sec) (numberp n))
    (error "format-time arguments must be numbers."))
  (flet ((pad (x) (if (< x 10) (format nil "0~a" x) x)))
    (let* ((hh (truncate (/ sec 3600)))
           (mm (truncate (/ sec 60)))
           (ss (- sec (* mm 60)))
           (old-format *float-format*)
           rslt)
      (setf mm (- mm (* hh 60)))
      (setf *float-format* (format nil "%.~af" n))
      (setf rslt (format nil "~a:~a:~a" hh (pad mm) (pad ss)))
      (setf *float-format* old-format)
      rslt)))

Oh boy, I posted my testing version which doesn’t actually get the time and uses a test value. Here’s the version I actually finished with yesterday:

;version 4
;debugflags trace
; get selection start in ms
(setq ms (get '*selection*' start))
; ms.math.floor() so we can use rem (replacement for mod) later
(setq sec (truncate ms))
; only the decimals of ms (3 of them)
(setq msonly (truncate (* (- ms sec) 1000)))

(setq hrs (truncate (/ sec 3600)))
(setq sec (- sec (* hrs 3600)))
(setq min (truncate (/ sec 60)))
(setq sec (- sec (* min 60)))

(if (> hrs 0)
    (format t "~a:" hrs)
)
(if (< min 10)
    (format t "0~a:" min)
    (format t "~a:" min)
)
(if (< sec 10)
    (format t "0~a." sec)
    (format t "~a." sec)
)
(format t "~a" msonly)

""



Ah, cool. I did see some comparison that claimed mod/rem are different when negative numbers are involved but of course this doesn’t matter here.

Thanks! I did see float-format during my “research” but could not for the life of me figure out how to actually use it and was too many hours into my first steps with LISP, so I was just glad I could find a simplistic approach. :smiley:

My starting point actually was the function at Formatting number to specific format in Common Lisp - Stack Overflow but I had to make it simpler for me to make any progress.

Many thanks! Using your work, only adding an if block to hide HH if it’s == 0 the code is:

;version 4
;debugflags trace

(defun format-time(sec &optional (n 3))
  ;; Return time formatted to hh:mm:ss + n decimal places.
  (unless (and (numberp sec) (numberp n))
    (error "format-time arguments must be numbers."))
  (flet ((pad (x) (if (< x 10) (format nil "0~a" x) x)))
    (let* ((hh (truncate (/ sec 3600)))
           (mm (truncate (/ sec 60)))
           (ss (- sec (* mm 60)))
           (old-format *float-format*)
           rslt)
      (setf mm (- mm (* hh 60)))
      (setf *float-format* (format nil "%.~af" n))
      (if (> hh 0)
          (setf rslt (format nil "~a:~a:~a" hh (pad mm) (pad ss)))
          (setf rslt (format nil "~a:~a" (pad mm) (pad ss)))
      )
      (setf *float-format* old-format)
      rslt)))

; get selection start in ms
(format t (format-time (get '*selection*' start) 3))

""

Macro export:

SelectTime:End="0.5" RelativeTo="Selection" Start="0"
NyquistPrompt:Command=";version 4\n;debugflags trace\n\n(defun format-time(sec &optional (n 3))\n  ;; Return time formatted to hh:mm:ss + n decimal places.\n  (unless (and (numberp sec) (numberp n))\n    (error \"format-time arguments must be numbers.\"))\n  (flet ((pad (x) (if (< x 10) (format nil \"0~a\" x) x)))\n    (let* ((hh (truncate (/ sec 3600)))\n           (mm (truncate (/ sec 60)))\n           (ss (- sec (* mm 60)))\n           (old-format *float-format*)\n           rslt)\n      (setf mm (- mm (* hh 60)))\n      (setf *float-format* (format nil \"%.~af\" n))\n      (if (> hh 0)\n          (setf rslt (format nil \"~a:~a:~a\" hh (pad mm) (pad ss)))\n          (setf rslt (format nil \"~a:~a\" (pad mm) (pad ss)))\n      )\n      (setf *float-format* old-format)\n      rslt)))\n\n; get selection start in ms\n(format t (format-time (get '*selection*' start) 3))\n\n\"\"" Parameters=""
CursSelStart:

Added some screenshots of what it looks like. I have it shortcut-assigned to “t”.
Screenshot_43.png
Screenshot_42.png

Yes, and no it doesn’t matter here :wink:


I recall having the same experience when I started with Nyquist, but there was less documentation available then. There’s a forum topic here where we (mostly edgar-rtf) were figuring out how to use it: Additional *float-format* options


You did well. Please don’t take my comments as criticisms, they are just tips from someone that has worked with Nyquist for a long time. Hopefully they will save you a lot of time and effort figuring out how to use Nyquist.


Do you really want minutes to be “00” when the selection is less than 60 seconds?
Maybe something like this:

;version 4
;debugflags trace

(defun format-time(sec &optional (n 3))
  ;; Return time formatted to hh:mm:ss + n decimal places.
  (unless (and (numberp sec) (numberp n))
    (error "format-time arguments must be numbers."))
  (flet ((pad (x) (if (< x 10) (format nil "0~a" x) x)))
    (let* ((hh (truncate (/ sec 3600)))
           (mm (truncate (/ sec 60)))
           (ss (- sec (* mm 60)))
           (old-format *float-format*)
           rslt)
      (setf mm (- mm (* hh 60)))
      (setf *float-format* (format nil "%.~af" n))
      (setf rslt (format nil "~a~a~a"
                         (if (> hh 0)
                             (format nil "~a:" hh)
                             "")
                         (if (> hh 0)
                             (format nil "~a:" (pad mm))
                             (if (> mm 0)
                                 (format nil "~a:" mm)
                                 ""))
                         (if (> mm 0) (pad ss) ss)))
      (setf *float-format* old-format)
      rslt)))

; get selection start in ms
(format t (format-time (get '*selection*' start) 3))

""

Some time ago I wrote a more complex version with a lot more formatting options. You may find it interesting. It’s here on my blog: https://audionyq.com/display-time-as-hhmmss/
One of the take-aways from this is that manipulating text is a pain in Nyquist :smiley: (but of course the main purpose of Nyquist is for manipulating audio rather than text).

If you are only printing the time, it would be better to simply return the time string, and Audacity will display it in a message box (rather than using the debug output).

To do that, change:

; get selection start in ms
(format t (format-time (get '*selection*' start) 3))

""

to:

; get selection start in ms
(format nil (format-time (get '*selection*' start) 3))

(format t …) outputs to the debug window, whereas (format nil …) simply returns the string.

Note that it may not work correctly in current versions of Audacity - it was written over 9 years ago. (I should really update it when I get time).

Update:
Just tried it, and actually it does still work, though I wonder why I used (optional) positional arguments rather than keyword arguments. I’d probably have used keyword arguments if I were writing it now.

I’m using this macro for finding mistakes in long recordings to annotate the text document, so I need to instantly copy-paste the result. The text box (pictured in screenshot) content can’t be copied (Windows 10 if that plays any role).

Although, if there is a way to combine this with writing directly to the clipboard, this might be interesting. I definitely need the visual feedback that I successfully activated the macro either way.

I think it might also be possible to output to a file, which in combination with a live-updating text editor might be interesting ( NPP in my case windows - How to automatically reload modified files in Notepad++ - Super User )

For now my process is:

  • Read in word processor while listening in Audacity until there’s a mistake
  • pause, click to mistake location and press t, ctrl+a, ctrl+c
  • select affected text in word processor, press whatever the shortcut for a new comment is and ctrl+v, space, then write a description of the issue

Later the comments get exported to a spreadsheet via Python script but having the comments in-text has proven more efficient to the voice talent.
Screenshot_44.png

Yes, that’s an old issue on Windows. Using the debug output is probably the best workaround.


There isn’t. Nyquist cannot access the Windows clipboard.


Nyquist can write to files, but it can’t “append” to files. If you need to append a file (write to the end of existing text), then the file has to be read into ram, appended, then written back to the file (a bit awkward, but can be done).

Another limitation for writing files is that Nyquist won’t prompt you for the file name / path, That has to be set before the code runs (either hard coded into the plug-in, or entered via a control in a GUI plug-in.

I think it would be difficult to improve on that workflow.