Could you add to Rhythm track new beat sounds?
What sounds do you want?
Whichever midi sounds can be produced and changed with pitch of strong beat and weak beat.
Rhythm Track does not use a MIDI synth. The sounds are programmed into the Rhythm Track plug-in. For example, this is the code that generates the “Ping” sound:
(defun get-ping (pitch ticklen)
(stretch-abs ticklen
(mult
(control-srate-abs *sound-srate* (pwl 0.005 amp 0.995 amp 1))
(osc pitch))))
and this is the code that generates the “Metronome Tick” sound:
;; Metronome tick by Steve Daulton.
(defun metronome-tick (hz peak)
(let* ((ln 300)
(sig-array (make-array ln))
(x 1))
;; generate some 'predictable' white noise
(dotimes (i ln)
(setf x (rem (* 479 x) 997))
(setf (aref sig-array i) (- (/ x 500.0) 1)))
(setf sig (sim (s-rest-abs 0.2)
(snd-from-array 0 44100 sig-array)))
(setf sig
(mult (abs-env (pwev 10 (/ ln 44100.0) 2 1 0))
(highpass8 (lowpass2 sig (* 2 hz) 6)
hz)))
(let ((gain (/ (peak sig 300))))
; The '1.11' factor makes up for gain reduction in 'resample'
(mult (abs-env (pwlv 1.11 0.02 1.11 0.05 0 ))
(jcrev (mult peak gain sig) 0.01 0.1)))))
To add more sounds, the sounds have to be programmed into the plug-in.
Could you add more variations of beats with new programming?
Yes I could, and with a bit of effort I expect that you could too
You’ve not said what sort of sounds you would like. Some sounds are much easier to synthesize than others. The “metronome tick” is fairly complex (as you can see from the length of the code), where as the “Ping” sound is much simpler. Synthesizing “real world” sounds can be extremely complex, though it is possible to use “sampling” rather than “synthesis”.
The easiest ones you can program which are different from the ones already in rhythm track.
OK, let’s see how to do this.
To modify the plug-in code you will need a plain text editor that does not mess up the formatting. For Windows I’d recommend NotePad++ which is available (free) here: https://notepad-plus-plus.org/
NotePad++ will work OK “out of the box”, but a couple of configuration options that will be very helpful:
- Ensure that “Parentheses matching” is enabled.
- Ensure that line numbering is enabled.
- Set the code language to “LISP”
- Set the indentation and tab settings to use 2 spaces.
Here’s the full code for Rhythm Track: https://github.com/audacity/audacity/blob/master/plug-ins/rhythmtrack.ny
You can use the version of rhythmtrack.ny that is in your Audacity Plug-ins folder, but I would recommend making a backup copy in case you mess up
The “tick generators” start from line 115.
We will add a simple “beep” sound lasting 0.1 seconds.
See from line 149, the type of sound is selected:
;; Make selected click
(defun click (type accent)
(setq pitch (if (= accent 1) high low))
(setq amp (if (= accent 1) 0.75 0.5))
(case type
(0 (get-metronome-tick (step-to-hz pitch) amp))
(1 (get-ping pitch 0.01))
(2 (get-ping pitch 0.08))
(3 (get-cowbell pitch))
(4 (get-resonant-noise pitch))
(5 (get-noise-click pitch))
(6 (get-drip pitch 0.007))
(t (get-drip pitch 0.1))))
For simplicity, we will add our “beep” to the end of the list of sounds:
;; Make selected click
(defun click (type accent)
(setq pitch (if (= accent 1) high low))
(setq amp (if (= accent 1) 0.75 0.5))
(case type
(0 (get-metronome-tick (step-to-hz pitch) amp))
(1 (get-ping pitch 0.01))
(2 (get-ping pitch 0.08))
(3 (get-cowbell pitch))
(4 (get-resonant-noise pitch))
(5 (get-noise-click pitch))
(6 (get-drip pitch 0.007))
(7 (get-drip pitch 0.1))
(t (get-beep pitch))))
The “t” option is used as the final option as a catch-all. We could write:
(8 (get-beep pitch))))
as the last line, but it is best practice to use a catch-all as the final option.
Now we probably want the first beat of the bar to be a bit louder than the other beats. We can do that in the same way as is done with the metronome tick, by multiplying the sound by an amplification factor “amp”:
(t (mult (get-beep pitch) amp))))
The “type” of tick (the “beat sound”) is selected by this rather complicated control line at line 31:
$control click-type (_ "Beat sound") choice (("Metronome" (_ "Metronome Tick"))
(_ "Ping (short)")
(_ "Ping (long)")
(_ "Cowbell")
("ResonantNoise" (_ "Resonant Noise"))
("NoiseClick" (_ "Noise Click"))
(_ "Drip (short)")
(_ "Drip (long)")) 0
This is more complicated than most user made plug-ins because it uses a special syntax for translations. We won’t be using translations for this, so we can simplify the control like this:
;control click-type "Beat sound" choice "Metronome Tick,Ping (short,Ping (long),Cowbell,Resonant Noise,Noise Click,Drip (short),Drip (long)" 0
The above control line creates a “Choice Widget” (a drop-down multiple choice control). More information about this here: Missing features - Audacity Support
So now we need to add an extra option for our new “beep” sound:
;control click-type "Beat sound" choice "Metronome Tick,Ping (short,Ping (long),Cowbell,Resonant Noise,Noise Click,Drip (short),Drip (long),Beep" 0
Note that the “0” at the end sets the default choice selection. If we want our new Beep to be the default, we can change that to an “8”:
;control click-type "Beat sound" choice "Metronome Tick,Ping (short,Ping (long),Cowbell,Resonant Noise,Noise Click,Drip (short),Drip (long),Beep" 8
If we try running the new code using the Debug button, we will see an error message:
Nyquist returned the value: 0.500000
followed by the debug information:
error: unbound function - GET-BEEP
...
In the “;; Make selected click” section, we called a new function GET-BEEP, but we have not yet defined that function, so let’s do that now.
At around line 140 we have the function
(defun get-cowbell (pitch)
(mult 0.8 (cowbell (step-to-hz pitch))))
We will add our new function GET-BEEP just below that.
Here’s the new function:
(defun get-beep (pitch)
(osc pitch 0.1))
OSC is a built in function that generates a sine tone (a beep) at the specified pitch and duration. More about OSC here: Nyquist Functions
If you have been following this mini-tutorial carefully, your version of rhthmtrack.ny should now look like this:
$nyquist plug-in
$version 4
$type generate
$name (_ "Rhythm Track")
$manpage "Rhythm_Track"
$preview linear
$action (_ "Generating Rhythm...")
$author (_ "Dominic Mazzoni")
$release 3.0.0
$copyright (_ "Released under terms of the GNU General Public License version 2")
;; by Dominic Mazzoni, David R. Sky and Steve Daulton.
;; Drip sound generator by Paul Beach
;; TODO: add more drum sounds
;; Released under terms of the GNU General Public License version 2:
;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
;;
;; For information about writing and modifying Nyquist plug-ins:
;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
$control tempo (_ "Tempo (bpm)") real (_ "30 - 300 beats/minute") 120 30 300
$control timesig (_ "Beats per bar") int (_ "1 - 20 beats/measure") 4 1 20
$control swing (_ "Swing amount") float (_ "+/- 1") 0 -1 1
$control text (_ "Set 'Number of bars' to zero to enable the 'Rhythm track duration'.")
$control bars (_ "Number of bars") int (_ "1 - 1000 bars") 16 0 1000
$control click-track-dur (_ "Rhythm track duration") time (_ "Used if 'Number of bars' = 0") 0 0 nil
$control offset (_ "Start time offset") time (_ "Silence before first beat") 0 0 nil
;control click-type "Beat sound" choice "Metronome Tick,Ping (short,Ping (long),Cowbell,Resonant Noise,Noise Click,Drip (short),Drip (long),Beep" 8
$control high (_ "MIDI pitch of strong beat") int (_ "18 - 116") 84 18 116
$control low (_ "MIDI pitch of weak beat") int (_ "18 - 116") 80 18 116
;; Helper functions:
(defun round-up (x)
(if (> x (truncate x))
(truncate (1+ x))
(truncate x)))
;; Filtering causes changes amplitude, so we normalize to
;; achieve a predictable level.
(defun normalize (sound)
(scale (/ (peak sound ny:all)) sound))
(defun s-rest-abs (d)
(abs-env (s-rest d)))
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Click sound synthesis
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Drip sound by Paul Beach www.proviewlandscape.com/liss/
(defun drip (p) ;p is pitch in hz
(let* ((maxhz (/ *sound-srate* 2.1))
(hz1 (min maxhz (* 2.40483 p)))
(hz2 (min maxhz (* 5.52008 p)))
(hz3 (min maxhz (* 8.653 p)))
(hz4 (min maxhz (* 11.8 p))))
(lp
(stretch-abs 1
(mult (exp-dec 0 0.015 0.25)
(sim
(mult (hzosc hz1) 0.5)
(mult (hzosc hz2) 0.25)
(mult (hzosc hz3) 0.125)
(mult (hzosc hz4) 0.0625))))
440)))
;; Metronome tick by Steve Daulton.
(defun metronome-tick (hz peak)
(let* ((ln 300)
(sig-array (make-array ln))
(x 1))
;; generate some 'predictable' white noise
(dotimes (i ln)
(setf x (rem (* 479 x) 997))
(setf (aref sig-array i) (- (/ x 500.0) 1)))
(setf sig (sim (s-rest-abs 0.2)
(snd-from-array 0 44100 sig-array)))
(setf sig
(mult (abs-env (pwev 10 (/ ln 44100.0) 2 1 0))
(highpass8 (lowpass2 sig (* 2 hz) 6)
hz)))
(let ((gain (/ (peak sig 300))))
; The '1.11' factor makes up for gain reduction in 'resample'
(mult (abs-env (pwlv 1.11 0.02 1.11 0.05 0 ))
(jcrev (mult peak gain sig) 0.01 0.1)))))
;; Cowbell by Steve Daulton.
(defun cowbell (hz)
(sim
(mult (pwev 0.3 0.8 0.0005)
(hzosc hz *tri-table*)
(hzosc (* hz 3.46) *tri-table*))
(mult (pwev 0.7 0.2 0.01)
(hzosc (* hz 7.3) *tri-table*)
(hzosc (* hz 1.52) *tri-table*))))
;; Single tick generators:
(defun get-metronome-tick (hz gain)
(resample
(sound-srate-abs 44100 (metronome-tick hz gain))
*sound-srate*))
(defun get-ping (pitch ticklen)
(stretch-abs ticklen
(mult
(control-srate-abs *sound-srate* (pwl 0.005 amp 0.995 amp 1))
(osc pitch))))
(defun get-resonant-noise (pitch)
(stretch-abs 0.05 ; 50 milliseconds
(mult
(control-srate-abs *sound-srate* (pwl 0.05 amp 0.95 amp 1))
(normalize (lowpass2 (noise 1) (step-to-hz pitch) 20)))))
(defun get-noise-click (pitch)
(stretch-abs 0.005
(mult
(control-srate-abs *sound-srate* (pwl 0.005 amp 0.995 amp 1))
(normalize (lowpass2 (noise 1) (step-to-hz pitch) 2)))))
(defun get-drip (pitch ticklen)
(stretch-abs ticklen
(mult
(control-srate-abs *sound-srate* (pwl 0.005 amp 0.995 amp 1))
(normalize (drip (step-to-hz pitch))))))
(defun get-cowbell (pitch)
(mult 0.8 (cowbell (step-to-hz pitch))))
(defun get-beep (pitch)
(osc pitch 0.1))
;; Make selected click
(defun click (type accent)
(setq pitch (if (= accent 1) high low))
(setq amp (if (= accent 1) 0.75 0.5))
(case type
(0 (get-metronome-tick (step-to-hz pitch) amp))
(1 (get-ping pitch 0.01))
(2 (get-ping pitch 0.08))
(3 (get-cowbell pitch))
(4 (get-resonant-noise pitch))
(5 (get-noise-click pitch))
(6 (get-drip pitch 0.007))
(7 (get-drip pitch 0.1))
(t (mult (get-beep pitch) amp))))
(defun swing-adjust (i val)
(* val (/ 3.0) (rem (1+ i) 2)))
;Make one measure and save it in the global *measure*
(defun makemeasure ()
(setf *measure*
(sim
(s-rest (* timesig beatlen)) ;required for trailing silence
(click click-type 1) ;accented beat
(simrep (x (- timesig 1))
(at-abs (* beatlen (+ x 1 (swing-adjust x swing)))
(cue (click click-type 0))))))) ;unaccented beat
(defun samplecount (total)
;;; Return number of samples required to reach target
(defun lastsample (target)
(let ((required (- target total)))
(setf total target)
required))
(function lastsample))
(defun get-measure (barnum)
(let ((end (* (1+ barnum) (* timesig beatlen)))
required-samples)
;; Actual end time is integer samples
(setf end (round (* end *sound-srate*)))
(setf required-samples (funcall addsamples end))
(setf *measure* (set-logical-stop (cue *measure*)
(/ required-samples *sound-srate*))))
*measure*)
(defun make-click-track (bars mdur)
(seqrep (i bars) (cue (get-measure i))))
;;;;;;;;;;;;;;;;;
;; MAIN PROGRAM
;;;;;;;;;;;;;;;;;
(setf beatlen (/ 60.0 tempo))
;call function to make one measure
(makemeasure)
; If 'Number of bars' = 0, calculate bars from 'Rhythm track duration'.
(when (= bars 0)
(setq bars (/ click-track-dur (* timesig beatlen))))
;if previewing, restrict number of bars
(let ((preview (/ (get '*project* 'preview-duration)
(* timesig beatlen))))
(if (not (get '*track* 'view)) ;NIL if previewing
(setq bars (min preview bars))))
;round up number of bars
(setq bars (round-up bars))
;; Calculate LEN for progress bar.
(setq len (/ (* 60.0 *sound-srate* timesig bars) tempo))
;; Initialize sample count
(setf addsamples (samplecount 0))
(if (< bars 1)
(_ "Set either 'Number of bars' or
'Rhythm track duration' to greater than zero.")
(if (get '*track* 'view) ;NIL if previewing
(seq (s-rest offset)
(make-click-track bars (* timesig beatlen)))
;; Don't preview the offset (silence).
(make-click-track bars (* timesig beatlen))))
and if you run it with the default settings it should sound like this:
Was my reply useful for you soulfire ?
Sorry to chime in here (yes, pun intended), that was a really great step by step “how to”, thanks Steve.
Going to try a few new sounds myself, don’t really have a use for them, but great for learning even more
techniques in Nyquist.
What would be really great, the ability to play short samples instead, but will start a new thread for that.