Beats Per Minute

hello,

We are doing a project for our final year. The aim of our project is to calculate the Beats Per Minute ie tempo of a sound clip. According to the solution that we have thought of, we require to save the values of t1, t2, t3 (refer to the figure attached) ie the instants where the sound wave goes above a particular threshold in decibels that will be given as input and the instany where the sound wave goes below the threshold value. We also require to save the peak values p1, p2… (refer to the figure attached) that will be considered as beat.

If this is a success, we will be contributing our work to audacity.

The following is the link for the image to be refered.
https://plus.google.com/photos/111381929354498427442/albums/5982921943066307665/5982921946881729042?enfplm&hl=en&utm_source=lmnavbr&utm_medium=embd&utm_campaign=lrnmre&rtsl=1&pid=5982921946881729042&oid=111381929354498427442

Hoping for support.
Thanks in advance.

Same school as these guys? https://forum.audacityteam.org/t/working-with-audio-samples/33015/1
See that topic for information about how one possible approach using the Nyquist scripting language.
Please pass that link on to your class mates so that we don’t need to keep answering the same question :wink:

Out of interest, have you been specifically asked to do this as a Nyquist plug-in, or in some other way?

Hello,
I am extremely sorry for the inconvenience caused to you. Yes we have been specifically asked to add as a nyquist plugin which would calculate the beats per minute. The link that you have given, we tried to run that code but it is not working as per our expectation. Our specification has been mentioned in the previous post. Please can you help us with the nyquist coding which would give us the time at that instant of sound as shown in the diagram from the previous post.


Thanking you.

I don’t think it would be right for me to do your homework for you - do you?
I’m happy to help you with your coding. How far have you got so far?

That’s what we are asking you for. We just want you to help us with the coding. For now we just want to know if there is any function which can help us retrieve a time instance and store it in a list. We want to locate the beats at equidistant points. We tried the coding by our own way but we are getting the unknown errors , Actually we are very new to nyquist coding so we are not understanding that we are going in which direction. So please if u could help us with our coding for given specifications.

There are several functions that can help with that, but no one function that does the whole task.
Did you read this topic? working with audio samples
There are a lot of tips and suggestions there.

What code? What errors?
If you post what you have written than we may be able to show where you have gone wrong.

hello,
Here we are attaching the code that we have tried

;nyquist plug-in
;version 1
;type analyze
;categories "http://audacityteam.org/namespace#OnsetDetector"
;name "Beats Per Minute"
;action "BPM..."
;codetype lisp

;control thresval "Threshold Percentage" int "" 85 5 100
(defvar cnt 10)
(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 ((time 0) (l NIL) (p T) (v (snd-fetch s2))) ((not v) l)


 (if (and p (> v thres)) (setq b (thres)) (setq cnt (+ cnt 1)))
 (setq p (< v thres))
 (setq time (+ time 1))
 (setq v (snd-fetch s2)))

In the above code we are trying to count the number of beats above the threshold. But here in the code time varaible is not incremented by 1 & we are getting the following error,
error: unbound function - THRES
if continued: try evaluating symbol again
Function: #<FSubr-SETQ: #af32250>
Arguments:
B
(THRES)
Function: #<FSubr-IF: #af3174c>
Arguments:
(AND P (> V THRES))
(SETQ B (THRES))
(SETQ CNT (+ CNT 1))
Function: #<FSubr-DO: #af317a0>
Arguments:
((B 0) (L NIL) (P T) (V (SND-FETCH S2)))
((NOT V) L)
(IF (AND P (> V THRES)) (SETQ B (THRES)) (SETQ CNT (+ CNT 1)))
(SETQ P (< V THRES))
(SETQ B (+ B 1))
(SETQ V (SND-FETCH S2))
1>

After running the plugin the selected waveform part gets cut & we are getting this error in the debug prompt.

The above code is written in .ny file in plugins folder , Is this the right way to add the plugin or anything else is supposed to be added ?

So please can u help us how to save the time values where the wave goes above & below the input threshold value. Can u tell us exactly what functions should be used & where we are going wrong in our code ?

Thanking you.

I’ve reformatted your code with some line breaks and indentations to make it more easily readable.
See here for advice about indenting LISP code: Conventions for Nyquist Plug-ins - #16 by steve
Any line numbers that I quote will refer to this version:

;nyquist plug-in
;version 1
;type analyze
;categories "http://audacityteam.org/namespace#OnsetDetector"
;name "Beats Per Minute"
;action "BPM..."
;codetype lisp

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

(defvar cnt 10)

(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 ((time 0) (l NIL) (p T) (v (snd-fetch s2)))
    ((not v) l)
  (if (and p (> v thres))
      (setq b thres)
      (setq cnt (+ cnt 1)))
  (setq p (< v thres))
  (setq time (+ time 1))
  (setq v (snd-fetch s2)))



Line 30:

      (setq b (thres))

should be:

      (setq b thres)

because THRES is a variable, not a function.

I’d also suggest using a different name for your variable “MAX” as “MAX” is a function name in Nyquist/XLisp (XLISP max)
and not using single character variable names, especially not the character “l” which can easily be confused with the number “1” in some fonts.

Questions to consider:
What do the variables “l”, “p”, “b” and “v” represent?
I can guess that you intend for “time” to represents the time value of the current sample and that “thresval” is your threshold value.


Yes.


“p” is set to true [T] when the sample value is below the threshold.
“p” is set to false [NIL] when the sample value is above the threshold.
In line 29:

  (if (and p (> v thres))

you test to see if the threshold has been crossed “upward” (from below, to above the threshold). This is the point at which you need to capture the time.
I don’t know what “b” is intended to represent. You have set it equal to the threshold value. Why?

If you want to store a list of times, you need to add the current time value to the list. For example:

(setf time-list ()) ; an empty list.
(setq time 3)       ; an arbitrary time value.

; add the value of TIME to the list:
(setf time-list (cons time time-list))
(print time-list)   ; prints the list to the debug window: (3)

(setq time 7)       ; another arbitrary time value.
(setf time-list (cons time time-list))
(print time-list)   ; prints: (7 3)

It is a little cumbersome to write (setf lis (cons val lis)) each time, but fortunately there is a useful abbreviation (see: http://www.cs.cmu.edu/~rbd/doc/nyquist/part13.html#index1000):

(setf time-list ()) ; an empty list.
(setq time 3)       ; an arbitrary time value.

; add the value of TIME to the list:
(push time time-list)
(print time-list)   ; prints the list to the debug window: (3)

(setq time 7)       ; another arbitrary time value.
(push time time-list)
(print time-list)   ; prints: (7 3)

Currently your TIME value is just a sample count. In order to convert that to real time in seconds, you need to divide by the sample rate of the audio that you are analyzing [S2]. You can get the sample rate of S2 with the function SND-SRATE Nyquist Functions

Hello,
Thank you so much for uo help, we have got an idea to move forward. And ‘b’ is the ‘time’ variable , at 1 place we forgot to change it , so sorry for that. But after running the same code with few changes suggested by you, we are getting ‘Nyquist did not return audio’ error. Can u please tell the reason for that ?

For your reference ,below is our code we implemented,

;nyquist plug-in
;version 1
;type analyze
;categories "http://audacityteam.org/namespace#OnsetDetector"
;name "Beats Per Minute"
;action "BPM..."
;codetype lisp

;control thresval "Threshold Percentage" int "" 85 5 100
(defvar cnt 10)
(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 ((time 0) (l NIL) (p T) (v (snd-fetch s2)))
    ((not v) l)
  (if (and p (> v thres))
      (setq cnt (+ cnt 1)))
  (setq p (< v thres))
  (setq time (+ time 1))
  (setq v (snd-fetch s2)))

Thanking you.

Your code returns “l” which you set to NIL at the top of the DO loop.

(do ((time 0) (l NIL) (p T) (v (snd-fetch s2)))
    ((not v) l)

The first line above is the start of the loop. Local variables TIME, L, P and V are initialised to 0, NIL, TRUE, and (SND-FETCH S2) respectively.
The second line is the “test” [ is V FALSE ?] and the return value. The “return value” is the thing that is returned when the loop exits.
See here: http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/xlisp/xlisp-ref/xlisp-ref-093.htm

Nowhere in the loop do you change the value of “L”, so it is still NIL when the loop exits.
I presume what you want to do is to use “L” as a list and push the time values into it.
See my previous post about storing the time values.

By the way, could you avoid using giant text. It makes it difficult to read on a small monitor. If forum visitors want big text they can set that in their browser.

hi,
Following is the code that we tried for retrieving the time.

;nyquist plug-in
;version 1
;type analyze
;categories "http://audacityteam.org/namespace#OnsetDetector"
;name "Beat Per Minute"
;action "BPM..."
;; 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 ((time 0.0) (listB NIL) (checkval 1) (belowThres T) (val (snd-fetch s2))) ((not val) listB)
 (if (and(and belowThres(> val thres)) (= checkval 1))
      (progn
	     (setq listB (cons (list time "B") listB))
	(setf sampv1 (if (arrayp val) (snd-add (aref val 0) (aref val 1)) val))
(snd-srate sampv1)
(snd-t0 sampv1)
	     (setq checkval 0)     
      )
 )
 (if (and belowThres (< val thres)) 
	(setq checkval 1) 
 )
(setq belowThres (< val thres))
(setq time (+ time 0.001))
(setq val (snd-fetch s2)) 
)

We are getting the following error:
error: bad argument type - 0.469504
Function: #<Subr-SND-SRATE: #aa82998>
Arguments:
0.469504
Function: #<FSubr-PROGN: #aa7d750>
Arguments:
(SETQ LISTB (CONS (LIST TIME “B”) LISTB))
(SETF SAMPV1 (IF (ARRAYP VAL) (SND-ADD (AREF VAL 0) (AREF VAL 1)) VAL))
(SND-SRATE SAMPV1)
(SND-T0 SAMPV1)
(SETQ CHECKVAL 0)
Function: #<FSubr-IF: #aa7d840>
Arguments:
(AND (AND BELOWTHRES (> VAL THRES)) (= CHECKVAL 1))
(PROGN (SETQ LISTB (CONS (LIST TIME “B”) LISTB)) (SETF SAMPV1 (IF (ARRAYP VAL) (SND-ADD (AREF VAL 0) (AREF VAL 1)) VAL)) (SND-SRATE SAMPV1) (SND-T0 SAMPV1) (SETQ CHECKVAL 0))
Function: #<FSubr-DO: #aa7d6c0>
Arguments:
((TIME 0) (LISTB NIL) (CHECKVAL 1) (BELOWTHRES T) (VAL (SND-FETCH S2)))
((NOT VAL) LISTB)
(IF (AND (AND BELOWTHRES (> VAL THRES)) (= CHECKVAL 1)) (PROGN (SETQ LISTB (CONS (LIST TIME “B”) LISTB)) (SETF SAMPV1 (IF (ARRAYP VAL) (SND-ADD (AREF VAL 0) (AREF VAL 1)) VAL)) (SND-SRATE SAMPV1) (SND-T0 SAMPV1) (SETQ CHECKVAL 0)))
(IF (AND BELOWTHRES (< VAL THRES)) (SETQ CHECKVAL 1))
(SETQ BELOWTHRES (< VAL THRES))
(SETQ TIME (+ TIME 0.001))
(SETQ VAL (SND-FETCH S2))
1>

The bad argument type that we are getting, is that something related to time? coz, in the code we have taken a new sound variable sampv1 for which we want the start time. When sound s2 goes above the threshold, the sound at that instance is intended to be stored in sampv1. Have we done it correctly?

I’d highly recommend using a text editor that has parentheses matching, then you won’t need to count the parentheses “( )” or leave them trailing in mid air.
If you’re on Windows I’d recommend NotePad++ (free). It also has Syntax highlighting for Lisp, which is close enough to Nyquist to be useful.

The idea of indenting Lisp code is so that you can see which arguments belong to which functions. Note that in your DO loop you have a list of local variables assigned to values. It is much easier to “read” these if they are a list one below the other. Have another look at that page that I sent the link for about indenting code. If the code is written clearly it is much easier to spot errors.

I’ve added some comments to your code indicating the main problems.

;nyquist plug-in
;version 1
;type analyze
;categories "http://audacityteam.org/namespace#OnsetDetector"
;name "Beat Per Minute"
;action "BPM..."
;; 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-sig (peak (signal) NY:ALL))
(setq thres (* (/ thresval 100.0) max-sig))
(setq s2 (signal))

(do ((time 0.0)
     (listB ())
     (checkval 1)
     (belowThres T)
     (val (snd-fetch s2)))  ; snd-fetch only works on sounds, not arrays
    ((not val) listB)
  (if (and (and belowThres (> val thres))
           (= checkval 1))
      (progn
        (setf listB (cons (list time "B") listB))
        ;; What is SAMPV1 for?
        (setf sampv1
          ; val cannot be an array. It is a number [float]
          (if (arrayp val)
              (snd-add (aref val 0) (aref val 1))
              val))
        ;; sampv1 is not a sound. it is a number.
        ;(snd-srate sampv1)
        ;(snd-t0 sampv1)
        (setq checkval 0)))
  (if (and belowThres (< val thres))
      (setq checkval 1))
  (setq belowThres (< val thres))
  (setq time (+ time 0.001))
  (setq val (snd-fetch s2)))