Beat Detector Plugin Code Question

;nyquist plug-in
;version 1
;type analyze
;categories "http://audacityteam.org/namespace#OnsetDetector"
;name "Beat Finder..."
;action "Finding beats..."
;author "Audacity"
;copyright "Released under terms of the GNU General Public License version 2"

;; Released under terms of the GNU General Public License version 2:
;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 

;control thresval "Threshold Percentage" int "" 65 5 100
(setf s1 (if (arrayp s) (snd-add (aref s 0) (aref s 1)) s))
(defun signal () (force-srate 1000 (lp (snd-follow (lp s1 50) 0.001 0.01 0.1 512) 10)))
(setq max (peak (signal) NY:ALL))
(setq thres (* (/ thresval 100.0) max))
(setq s2 (signal))
(do ((c 0.0) (l NIL) (p T) (v (snd-fetch s2))) ((not v) l)
 (if (and p (> v thres)) (setq l (cons (list c "B") l)))
 (setq p (< v thres))
 (setq c (+ c 0.001))
 (setq v (snd-fetch s2)))

Hey guys,

I’m kind of new to coding and I’m trying to walk through this lisp code. This is beat.ny, which I assume does beat detection, ie, marking the audio spikes over a certain threshold.

I’m trying to figure out how the threshold detection takes place. Namely: if it’s more than merely when a certain sample goes over a threshold – is there some kind of “root mean square” or weighted average going on?

I think the particular line of interest is this, which I’m not sure how to interpret. It seems to be doing a running average?

(defun signal () (force-srate 1000 (lp (snd-follow (lp s1 50) 0.001 0.01 0.1 512) 10)))

Thanks! :smiley:

It’s probably not the easiest code to walk through, but let’s take it a bit at a time [and I’ll reformat it as we go]:

;control thresval "Threshold Percentage" int "" 65 5 100

Sets the user control variable “threshval”. Default 65, slider range 5 to 100 [integer].


(setf s1
  (if (arrayp s)
      (snd-add (aref s 0) (aref s 1))
      s))

If the sound “S” is an array [stereo sound], add the left and right channels together [Computationally cheap, but is this the best way to make it mono? Makes the “threshold” value completely different for mono/stereo]


(defun signal ()
  (force-srate 1000
    (lp (snd-follow (lp s1 50) 0.001 0.01 0.1 512)
        10)))

A function called “signal” [not very descriptive], is basically an “envelope follower”, that creates a signal that rides over the peaks of the sound. The sample rate is “forced” to 1000 Hz. The two low pass filters (lp …) combined with the envelope follower “snd-follow” causes the envelope to follow low frequencies, so it will tend to follow strong bass beats.


(setq max (peak (signal) NY:ALL))

Find the peak level of the sound returned by the function “signal”.


(setq thres (* (/ thresval 100.0) max))

Divide the user supplied “threshval” by 100, so as to give a value for “thresh” in the range 0 to 1.


(setq s2 (signal))

Set “S2” to the sound returned by the function “signal” [why do that here? We have now computed “signal” twice.]


(do ((c 0.0)
     (l NIL)
     (p T)
     (v (snd-fetch s2)))
    ((not v) l)
  (if (and p (> v thres))
      (setq l (cons (list c "B") l)))
  (setq p (< v thres))
  (setq c (+ c 0.001))
  (setq v (snd-fetch s2)))

A “Do” loop.
Sets local variables:
c = 0.0
l = NIL [I’m really not keen on using single letter variables, especially “l” which can be easily mistaken for “1”]
p = T [“T” is the Nyquist symbol for “boolean True”]
v = (snd-fetch s2) [set “v” to the value of the next sample]

(not v) This is the “test”. The loop will continue until “v” is NIL [when it runs out of samples]
((not v) l) The “l” is the return value of the loop.

(if (and p (> v thres)) if “p” is “true”, and “v” is less than the threshold value “thresh”…
(setq l (cons (list c “B”) l))) add the list (c “B”) [where “c” is the value of “c”, and ““B”” is the letter “B”] to the list “l” [“l” is initially NIL, but now becomes a list]

(setq p (< v thres)) If “v” is less than "thresh, then “p” is true, otherwise “p” is false.
(setq c (+ c 0.001)) Increment “c” by 0.001 [the time is seconds of one sample period for the “signal” as it has a sample rate of 1000 Hz]

(setq v (snd-fetch s2))) Fetch the next sample.


Does that help?

Yes it helps a great deal Steve, thanks!

You’re welcome. I look forward to seeing some plug-ins from you in due course, but if you get stuck, feel free to ask questions :wink: