Nyquist Drum Replacer

Using Nyquist scripts in Audacity.
Post and download new plug-ins.
Forum rules
If you require help using Audacity, please post on the forum board relevant to your operating system:
Windows
Mac OS X
GNU/Linux and Unix-like
Paul2
Posts: 151
Joined: Wed Sep 04, 2019 1:17 pm
Operating System: macOS 10.15 Catalina or later

Nyquist Drum Replacer

Post by Paul2 » Tue Jun 08, 2021 6:58 am

Hi Steve,

I have an idea for a plugin that will be useful to me, and probably others too.
However, before I embark on this coding journey, just wanted your opinion as to how do-able it would be.

Let's say we have a track, on which there are only drums, however I want to change the drum beat (sound not tempo)
with another.

This other (new) drum beat/sound can be from a file, or a single sound (or selection) from another track, or even a selection
at the beginning of the same track that will be modified, which ever is easiest.

Now the question is, how easy/difficult would it be to code some Xlisp to detect the drum beats and replace them with the new one?

It can be on a new track or by replacing the "old" drum beats on the existing track.

I have looked at existing Nyquist plugins, but nothing that would fit the bill.

Thanks.

steve
Site Admin
Posts: 81228
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Nyquist Drum Replacer

Post by steve » Tue Jun 08, 2021 10:19 am

In no particular order:
Paul2 wrote:
Tue Jun 08, 2021 6:58 am
how easy/difficult would it be to code some Xlisp to detect the drum beats
See "Beat Finder" https://manual.audacityteam.org/man/beat_finder.html
It works relatively well on single drum tracks.

You will see in the code that it uses a low-pass filter (LP sound) but if you test on a drum only track you may find that it works better without.

Code: Select all

(defun bass-tracker (sig)
  (let* ((bass (lp sig 50))
         ;(snd-follow sound floor risetime falltime lookahead)
         (follower (snd-follow bass 0.001 0.01 0.1 512)))
    (force-srate 1000 (lp follower 10))))
Paul2 wrote:
Tue Jun 08, 2021 6:58 am
This other (new) drum beat/sound can be from a file, or a single sound (or selection) from another track, or even a selection
at the beginning of the same track that will be modified, which ever is easiest.
From a WAV file is pretty easy, and a very flexible solution.
  • You could use a hard coded file path, such as "C:\User\Paul2\Documents\drums\default.wav" (easiest), but that makes it specific to your computer, and platform specific even if the user name is input through the plug-in's GUI.
  • You could use a text input widget to enter the full path to the WAV file, but that's awkward to use, especially on macOS.
  • You could use a File Button widget, which is probably the best option for users, but more tricky to code.
Another option could be to synthesize the drum sounds. Nyquist is very powerful for synthesizing sounds, though it can be very difficult to synthesize realistic sounds. The sounds in "Rhythm Track" are all synthesized: https://github.com/audacity/audacity/bl ... hmtrack.ny and "Risset Drum" is easily modified to create a range of synth-drum type sounds: https://github.com/audacity/audacity/bl ... setdrum.ny


Whichever method you use for getting the new drum sound, see SND-ONESHOT for a way to trigger the drum: https://www.cs.cmu.edu/~rbd/doc/nyquist ... l#index745


I would highly recommend that you break down you idea into smaller parts, and write each part as code that you can run and test individually. You will have at least 4 parts:
  1. Beat detection
  2. Importing / capturing synthesizing the new drum sound
  3. Triggering the sound
  4. The graphical interface (don't start on this until you have some working code for the other parts)
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

Paul2
Posts: 151
Joined: Wed Sep 04, 2019 1:17 pm
Operating System: macOS 10.15 Catalina or later

Re: Nyquist Drum Replacer

Post by Paul2 » Tue Jun 08, 2021 10:43 am

That is fantastic advise and information, thank you very much Steve.
You have given me loads of ideas and an excellent starting point.

Will update here on progress.

Thanks again.

steve
Site Admin
Posts: 81228
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Nyquist Drum Replacer

Post by steve » Tue Jun 08, 2021 10:57 am

I've just noticed in the beat.ny code:

Code: Select all

(force-srate 1000 ...
That's almost pointless imo. The only slightly useful thing that does is that in the final DO loop

Code: Select all

(do ((time 0.0 (+ time 0.001))
time can be incremented by a round decimal number.

Better in my opinion, would be to leave out the "force-srate" and calculate the actual sample rate of the sound returned from the BASS-TRACKER function.

Code: Select all

(let ((beats (bass-tracker (mix-to-mono *track*))))
  (setf rate (snd-srate beats))
  ...
Then, in the DO loop, you just need to count samples, and you can convert from samples to time in seconds with:
seconds = number-of-samples / sample-rate
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

Paul2
Posts: 151
Joined: Wed Sep 04, 2019 1:17 pm
Operating System: macOS 10.15 Catalina or later

Re: Nyquist Drum Replacer

Post by Paul2 » Tue Jun 08, 2021 11:49 am

Understood.

As you suggested, starting off by using, and looking at the code in the beat detect plugin.
On an isolated drum track, it works very well.
The test track is a "song" at 136 BPM (4/4 timing) and just over 3 minutes long.
There were only 22 errors, either missed beats (probably due to the threshold setting) and some double labels.
As a ratio, that error is very acceptable and quick and easy to fix.
Screen Shot 2021-06-08 at 1.50.17 PM.png
Screen Shot 2021-06-08 at 1.50.17 PM.png (33.88 KiB) Viewed 62 times
Now, just thinking out loud...wondering if there may be another way to approach this.
Since the timings are already known in the label track, wondering how easy it would be (or complete madness) to then
analyze the label track, create a new audio track and just place the new drum sound at each label start.
Screen Shot 2021-06-08 at 2.18.18 PM.png
Screen Shot 2021-06-08 at 2.18.18 PM.png (17.71 KiB) Viewed 62 times
Not saying it's easier, it's probably not, but just want to consider all options first.

Paul2
Posts: 151
Joined: Wed Sep 04, 2019 1:17 pm
Operating System: macOS 10.15 Catalina or later

Re: Nyquist Drum Replacer

Post by Paul2 » Tue Jun 08, 2021 1:08 pm

Ok, going with your initial idea Steve.

Paul2
Posts: 151
Joined: Wed Sep 04, 2019 1:17 pm
Operating System: macOS 10.15 Catalina or later

Re: Nyquist Drum Replacer

Post by Paul2 » Tue Jun 08, 2021 2:28 pm

Had a look at the "beat.ny" code, which is version 1 Xlisp syntax.

Where is the actual new label track being created in the code?

With version 4, it can be done this way:

Code: Select all

(aud-do "CursTrackStart:")
(aud-do "AddLabel:")
(aud-do (format nil "SetLabel: Text=~s" tname))
But version 1?

Is it perhaps this line?

Code: Select all

(if (and p (> v thres)) (setq l (cons (list c "B") l)))
And more specifically either "cons" and/or "list" ?
Searched for both of these but found no info like parameters they take.
I tried in the prompt:

Code: Select all

(cons (list 2 "B") 1) ; just a guess with the vars, assumed where I put the "2", it's a time offset and "1" is the length.
No error, but also no label track created.

steve
Site Admin
Posts: 81228
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Nyquist Drum Replacer

Post by steve » Tue Jun 08, 2021 4:11 pm

Paul2 wrote:
Tue Jun 08, 2021 2:28 pm
Where is the actual new label track being created in the code?
See this section on "Return Values": https://wiki.audacityteam.org/wiki/Nyqu ... urn_Values


With reference to the latest version of beat.ny (https://github.com/audacity/audacity/bl ... ns/beat.ny)

Code: Select all

(let ((beats (bass-tracker (mix-to-mono *track*))))
  (setf peak-sig (peak beats ny:all))
  (setf threshold (* threshold peak-sig))
  (do ((time 0.0 (+ time 0.001))
       (val (snd-fetch beats) (snd-fetch beats))
       (flag T)
       labels)
      ((not val) labels)
    (when (and flag (> val threshold))
      (push (list time "B") labels))
    (setf flag (< val threshold))))
A local variable called "labels" is declared in the DO loop. It has not been set to a value, so it has a value "NIL".
The 'NIL' constant represents an empty list or the boolean value "false". In this case it is used as an empty list.

Each time ("when") that "flag" is "true" and the value of "val" is greater than the value of "threshold", a list "(list time "B") is pushed onto the list "labels".
Thus, "labels" ends up as a list of lists, in the form:
(list (list t0 "B") (list t1 "B") ... (list tn "B"))

When that "list of lists" is returned to Audacity, Audacity interprets it as point labels.

"CONS" is described here: https://www.audacity-forum.de/download/ ... ef-080.htm

"PUSH" is shorthand (it's a LISP macro if I recall correctly).
(push val my-list) is equivalent to (setf my-list (cons val my-list))
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

steve
Site Admin
Posts: 81228
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Nyquist Drum Replacer

Post by steve » Tue Jun 08, 2021 4:22 pm

Paul2 wrote:
Tue Jun 08, 2021 2:28 pm

Code: Select all

(if (and p (> v thres)) (setq l (cons (list c "B") l)))
I know that you didn't write it, but that code is horrible.
What is that character after "(setq"? Is it the number "1", a lower case "L", what does it represent? It is not obvious, and it so easily could be.
What is the "test" in the IF statement? Don't make me count parentheses.

Much better would be something like

Code: Select all

(if (and is-peak (> val thres))
    (setf labels (cons (list t0 "B") labels)))
or better, since there is no "else" clause:

Code: Select all

(when (and is-peak (> val thres))
  (setf labels (cons (list t0 "B") labels)))
or

Code: Select all

(when (and is-peak (> val thres))
  (push (list t0 "B") labels))
9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

steve
Site Admin
Posts: 81228
Joined: Sat Dec 01, 2007 11:43 am
Operating System: Linux *buntu

Re: Nyquist Drum Replacer

Post by steve » Tue Jun 08, 2021 4:28 pm

A bit off-topic, but I thought that you might find this interesting.
It is a very simple (too simple to be very useful) implementation of an algorithm to calculate the tempo of a drum track. It works reasonable well with click tracks, but would need to be more sophisticated to work well with a real drum track.

The basic algorithm is well known, and is described here: https://sound.stackexchange.com/questio ... ually-work

Code: Select all

;nyquist plug-in
;version 4
;type analyze
;name "Guess Click Track Tempo"

(setf tracker-rate 200) ;target sample rate for tracker
(setf lo-bpm 30)
(setf hi-bpm 300)

;; Convert bpm to beats per seconds.
(setf lo-bps (/ lo-bpm 60.0))
(setf hi-bps (/ hi-bpm 60.0))

(defun peak-tracker (sig)
  ;; Returns a low sample rate control that tracks peaks.
  (setf step (round (/ *sound-srate* tracker-rate)))
  (snd-avg sig step step op-peak))


(let ((tracker (peak-tracker *track*))
      best-bps
      (best-peak 0))
  (do ((bps lo-bps (+ bps (/ 60.0))))
      ((> bps (1+ hi-bps)))
    (setf p0 (peak (comb tracker 20 bps) 2000))
    (when (> p0 best-peak)
      (setf best-peak p0)
      (setf best-bps bps)))
  (if (< (round (* 60 best-bps)) hi-bpm)
      (format nil "~a bpm" (* 60 best-bps))
      "BPM not found."))

9/10 questions are answered in the FREQUENTLY ASKED QUESTIONS (FAQ)

Post Reply