Add "Measure Average" to Analyze section

Would it be possible to complement “Measure RMS” with “Measure Average”. This would enable the direct calculation of the dissipation in a virtual amplifier with the selected music track.

What do you mean by “Average”?

“RMS” is an average (https://en.wikipedia.org/wiki/Root_mean_square)

Average or mean in this case would be the sum of all the absolute values of the samples in the music divided by the number of samples. RMS is the square root of the (sum of the sample values squared/number of samples). For example a sine wave, peak 1V, has an rms value of 0.7071 and an average value of 0.6366. For typical popular music, rms value = 0.30, average or mean value = 0.25. If you know both, you can calculate the dissipation in an amplifier with a fairly simple formula. This can be extended further by simulating a passive crossover using classic filters as in Audacity in order to find the dissipation in separate amplifiers driving woofers, mid range or tweeters for any music. This is very useful for the optimum design of amplifiers and loudspeakers etc.

For mono tracks, run this code in the Nyquist Prompt (https://manual.audacityteam.org/man/nyquist_prompt.html)

(defun calc-avg (sig)
    (sref (sound (mult (integrate sig)
                       (/ (get-duration 1))))
          (/ (1- len) *sound-srate*)))

(calc-avg (snd-abs *track*))

Here’s a version that works with mono or stereo. I’ve added lots of code comments to aid understanding.

(defun abs-avg (sig samples)
  ;;; Return average of absolute sample values.
  (flet ((mono-avg (mono-sig samples)
            ;; Local function calculates average for one channel.
            (let* ((srate (snd-srate mono-sig))
                   (dur (/ samples srate))
                   (integral (integrate (s-abs mono-sig)))
                   avg)
              ; Get final sample value of integral.
              (setf avg (snd-sref integral (/ (1- samples) srate)))
              ;Nyquist implementation of 'integrate' requires normalizing
              ; against duration (seconds).
              (/ avg dur))))
    (when (soundp sig)
      (return-from abs-avg (mono-avg sig samples)))
    ;; Else stereo
    (let ((left (mono-avg (aref sig 0) samples))
          (right (mono-avg (aref sig 1) samples)))
      (/ (+ left right) 2.0))))


; Test it...
(abs-avg *track* len)

Hello Steve,
Thank you for the Nyquist program, I assume the user has to split the stereo track into 2 mono tracks, is that correct ? I am using python and C programs ( and know nothing about LISP !) to do further analysis of music files which, I am relieved to say, give the same answers as your plugins. I will produce a white paper to open up discussions concerning some aspects of the design of amps and loudspeakers which will include the use of Audacity to derive the rms and mean values for use as described in the other posts.

Thank you again for your help
Phil

Not with the second version that I posted.
In the function “abs-avg”, notice the lines:

    (when (soundp sig)
      (return-from abs-avg (mono-avg sig samples)))

In the Nyquist language, a “sound” is a built-in data type. Stereo or multi-channel audio in Nyquist is an array of sounds. In the case of stereo, the first element of the array is the left channel sound, and the second element is the right channel sound.

When Audacity runs a Nyquist effect, it binds the selected audio to the variable TRACK.

(abs-avg *track* len)

This line passes TRACK to the function “abs-avg” as the first positional argument “sig”.
So if “sig” is a “sound”, the selected audio is mono. If “sig” is an array of sounds then it’s stereo.

SOUNDP is a predicate function that returns true if the argument is a sound, or false if it isn’t.
(Nyquist uses the symbol “T” for boolean true, and “NIL” for boolean false.)

    (when (soundp sig)
      (return-from abs-avg (mono-avg sig samples)))

When “sig” is a sound, we jump out of the ABS-AVG function immediately, returning the value calculated by (mono-avg sig samples).
If “sig” is not a sound (it’s an array of sounds), then we run:

    ;; Else stereo
    (let ((left (mono-avg (aref sig 0) samples))
          (right (mono-avg (aref sig 1) samples)))
      (/ (+ left right) 2.0))))

“(aref sig 0)” gets the first element from “sig” (the left channel sound).
“(aref sig 1)” gets the second element from “sig” (the right channel sound).

As left and right channels are the same length (the length of the selection), the number of samples in the left channel is the same as the number of samples in the right channel, so we can get the average for selected stereo from a simple average of the left and right channels:

(/ (+ left right) 2.0)

Note that for convenience, the script could be made into an installable plug-in, by adding the necessary “headers” to the script and saving as a plain text file with the file extension “.ny”. More information about this here: https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference

Thanks again Steve. If I have understood your explanation of the stereo version correctly, the end result is an average of the mean values for Left and Right. However I need the separate values for each track so would simply use the mono version on each mono track which is quite acceptable.

Do you mean, separate values for each channel? (A stereo track is one track with two channels)
If so, try this version:

;debugflags trace

(defun mono-avg (mono-sig samples)
  (let* ((srate (snd-srate mono-sig))
         (dur (/ samples srate))
         (integral (integrate (s-abs mono-sig)))
         (avg (snd-sref integral (/ (1- samples) srate))))
    ;Nyquist implementation of 'integrate' requires normalizing
    ; against duration (seconds).
    (print (/ avg dur))))

; Test it...
(multichan-expand #'mono-avg *track* len)
""

Yes Steve, that is perfect, thank you.