Exporting envelope of damped sinusoidal wave

I’m looking to export data points with time and linear amplitude of a damped sinusoidal wave (struck instrument string). I’m only interested in analyzing the (assumed) exponential decay of the vibration’s amplitude, so the envelope of the sinusoidal is really all I’m looking to export into Logger Pro/Excel. (not the wave patterning inside, just the peaks)

I found in another post here a Nyquist prompt to find the absolute peak amplitude in the time interval of one second, and it seemed very promising as I tried it out. Unfortunately, I can’t yet understand code (working on it!) and, therefore, I don’t know how to modify it to my purposes. I’m looking to export something along the lines of an average/peak amplitude of the sound wave in a time interval of 50-100ms. My samples are approximately 2 seconds long, and I’d need sufficient data points to be able to analyze the decay further.

So far my plans of attack have been 1) reading the amplitude and time manually and logging it into a data sheet (aLOT of grunt work) and 2) using sample data export and weeding out all but the peaks, only to find thousands of data points (more grunt work).

I don’t know if what I’m trying to do makes any sense/is possible at all. Any tips or differing ideas on how to examine the decay of a sound wave? All help very welcome.

Without getting too complicated, and assuming that you are working with a mono track, you could run this code in the Nyquist Prompt, then manually copy the data from the debug window:

;type analyze
;debugflags trace
;version 4

(setf *float-format* "%.4f")  ;print  with 4 decimal places

(let ((step (round (/ (snd-srate *track*) 100)))
      peak-follow)
  (setf peak-follow (snd-avg *track* step step op-peak))
  (setf *track* nil)
  (do ((val (snd-fetch peak-follow) (snd-fetch peak-follow)))
      ((not val) "")
    (print val)))

If this works for you and you would like to know exactly what it is doing, I’d be happy to explain the code.

Yes, I’m working with a mono track. It worked perfectly, thank you for the help! If you’re still willing I’d be happy to learn about what the code’s doing.

Super :slight_smile:

Sure, no problem. I’ll also give links to the documentation that gives more detail.


;type analyze
;debugflags trace
;version 4

This first part is what we call “plug-in headers”. Lines that begin with a semicolon are “comments” and are ignored by Nyquist, but there are certain special comments that are read by Audacity and tell Audacity how to set up the code’s environment:

;type analyze

This tells Audacity to treat the code as an “Analyze” type plug-in. Analyze type plug-ins are able to access the selected tracks, but generally do not modify the track.

;debugflags trace

This enables Nyquist’s debugging mode. Specifically it tells Audacity to display the debug window if there is any debug text to show.

;version 4

A standard header that says we are using the latest version of Nyquist code and headers. All modern code should be version 4.



(setf *float-format* "%.4f")  ;print  with 4 decimal places

Nyquist is a member of the LISP family of computer languages. It uses “S-expressions” (“symbolic expressions”), which, like other forms of LISP, has lots of parentheses.
Virtually all commands are in the form: (command arguments)
So, for example, for a simple math expression:
Y = 4 + 3
in Nyquist / LISP it would be written as:
(setf y (+ 4 3))
SETF is the command “set field”, which is a general way to set the value of something. The “arguments” (parameters) are “Y” and a nested function “(+ 4 3)”. Note that in the nested function, the function / command name is “+” and it’s arguments are “4” and “3”.
Going back to (setf float-format “%.4f”), we are setting the value of a special system variable *float-format" to a magic string value “%.4f”. In short, it means what it says in the comment: print numbers with 4 decimal places.
https://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-119.htm


(let ((step (round (/ (snd-srate *track*) 100)))
      peak-follow)
  (setf peak-follow (snd-avg *track* step step op-peak))
  (setf *track* nil)
  (do ((val (snd-fetch peak-follow) (snd-fetch peak-follow)))
      ((not val) "")
    (print val)))

LET XLISP let
We create two local variables, “step” and “peak-follow”.
The “step” variable is given the value (round (/ (snd-srate track) 100))

  • When reading nested expressions like this, it’s usually easiest to work from the inside (the most nested) part outwards:
  • TRACK is the selected track audio
    • SND-SRATE Returns the sample rate of the sound.
    • (/ (snd-srate track) 100) divide the sample rate of the selected audio by 100.
    • (round (/ (snd-srate track) 100)) Round to integer the “sample rate divided by 100”.

The “peak-follow” variable has not been assigned a value, so it’s value is implicitly NIL

(setf peak-follow (snd-avg *track* step step op-peak))

Here we set the value of “peak-follow” to the value returned by (snd-avg track step step op-peak)
SND-AVG in this case, we are stepping through the track audio in steps of 1/100th second and looking at the peak level within each step. “peak-follow” is therefore a “sound” that has a sample rate of 100 Hz, and each sample represents the peak value of a specific 0.01 second step.


  (do ((val (snd-fetch peak-follow) (snd-fetch peak-follow)))
      ((not val) "")
    (print val))

DO a loop structure.
Each time we loop through the code, the variable VAL is given the value of the next sample from “peak-follow”.
The code loops until “NOT VAL”, which means we loop until VAL is NIL.
The return value from the loop is an empty string “”. This is just to keep Audacity happy that we have returned a valid value, even though that value is essentially nothing.
(print val) Each time the code loops, the value of VAL is printed. Note that in Audacity, “PRINT” will write to the debug stream, so we see the full list of printed values in the debug window.