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.
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.
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:
Beat detection
Importing / capturing synthesizing the new drum sound
Triggering the sound
The graphical interface (don’t start on this until you have some working code for the other parts)
That’s almost pointless imo. The only slightly useful thing that does is that in the final DO loop
(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.
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
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.
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.
Not saying it’s easier, it’s probably not, but just want to consider all options first.
(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.
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.
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.
As regards the “beat.ny” code, makes perfect sense now, yes I agree that original code is a bit confusing.
Once I read your explanation and your code simplification, together with what is written in the page about return values:
When the return value is a character or string, a dialog window will appear with the data displayed as text.
Then further down…
If an appropriately formatted list is returned to Audacity, a label track will be created below the audio track(s).
Bingo!! The penny dropped.
The format is then “constructed” and “pushed” like: ((number “string”) (number “string”) … )
I also really like your code for the tempo.
Was going to surprise you by adding BPM, but I arrived at it via another way.
If you look at the table of values I posted earlier:
These values are in the “time” variable, so if we take the delta of any two consecutive values, invert
and multiply by 60, we get the BPM.
Taking the first two values:
(sorry, looks a bit convoluted as doing equations on the forum does not format too nicely)
That’s a very good way to do it, so long as you detect exactly one drum hit for each beat. It becomes rather complicated if the drum is playing some other rhythm, or if the beat detector misses some beats or adds in extra ‘false’ beats.
Yes, if there are false triggers or missing ones, then the BPM will be out.
To overcome this, could do three measurements at different times and take an average.
These measurements could be done at the beginning, middle and towards the end.
The timings will be in a list, which is essentially a formatted array, I’m assuming.
As for the drum sample/beat that will be replacing with, thought about something there as well.
The file option is not great due to the shortcomings of Nyquist.
Also, depending on the BPM of the song, it will have an effect on how long the decay of the drum can be.
So, even if the file option was OK, it would be difficult to visualize the length of the replacement drum sound.
Too long and it will be a mess.
So how about this:
Create a track, on this track put some candidate drum replacement sounds, with some silence between them.
With the original drum track underneath it, it’s now easy to compare them.
We can also preview all the individual candidate drum samples and, once one is chosen, we can manipulate it.
Shorten it, adjust tonal qualities, etc.
Once done, we can use that sample as the replacement sound.
Now, the question is, what is involved in selecting that wanted sound, run a Nyquist plugin, it stores it in an array then
into scratch.
Similar idea to your PUNCH/ROLL plugin.
Now we run the “main” plugin and the sound gets put in a new audio track at the right times.
Generator plug-ins will create a new mono track if there is no selection. It’s not currently possible for a normal Nyquist plug-in to create a stereo track.
Generate type plug-ins count time in “seconds” (;type process and ;type analyze stretch time so that “1 unit” of time is the length of the selection).
(defun get-sound (fname)
;;; Returns the audio file 'fname', else an
;;; error message if the file is not readable.
(let ((fp (open fname))) ;try opening the specified file
(cond
;; If 'fp' exists (is not NIL), the file exists.
;; We have opened the file, so close it before doing
;; anything else.
(fp (close fp)
(s-read filename)) ;Read the audio file with default options.
;; 'Else' return an error message.
(t (format nil "Error.~%~s~%cannot be opened."
fname)))))
;;; Add the sound at specified intervals for the
;;; specified number of times, and return the result.
(defun do-sequence (d-snd)
(let ((delay (/ 60.0 tempo)))
(simrep (i number)
(at (* i delay) (cue d-snd)))))
Considering how many options have been discussed on this thread, perhaps it’s best not to make one single plugin, but a few of them.
The general idea is to enable the creation of a simple song using only Audacity.
So one track for bass drum, another for hi-hats, cymbals etc.
Once those have been created, can mix them into a percussion stem (if need be), then move on to the synth track, guitar, vocals, etc.
What I have in mind then is:
One plugin to detect the drums and just create labels and possibly a BPM count as well.
This is optional and only needed if one wants to add to an existing song, change it or play along with it.
A variation on the above, but it inserts actual samples instead of just labels.
There is use for both types.
Another plugin, your simple drum machine to create the percussion tracks, one at a time.
A nice addition is to be able to include a pattern, i.e. cymbals only every third beat, etc.
This can then be used to add different instrument samples.
I like the idea of being able to record myself tapping a rhythm on a table, and then replace the tap sounds with a drum. It would be perfectly sufficient for each recorded track to be one drum only, and has the advantage that I can then mix the tracks as required. I’m not aware of an existing plug-in to do that.