audacity-plugins/ADDITIVE3.ny

133 lines
5.5 KiB
Plaintext
Raw Permalink Normal View History

2024-05-03 02:00:39 +00:00
;nyquist plug-in
;version 4
;type generate
;categories "http://lv2plug.in/ns/lv2core#GeneratorPlugin"
;preview linear
;name "Additive Tone..."
;action "Generating Tones..."
;author "dm"
;copyright "Released under terms of the GNU General Public License version 2"
;release 1.1
;control coefs-string "Decay Coefficients" string "" "(2.25 3 2.0 5 3) (3.3 2.7) (5.4 2 7.5 (3.2 5) (4 2)) (7.8 9.2) (13.3)"
;control pch "Pitch (Steps)" float-text "" 60 1 96
;control vib-rate "Vibrato Speed (Hz)" float-text "" 36 0 1102.4
;control vib-depth "Vibrato Depth (Hz)" float-text "" 3 0 nil
;control dur "Duration" time "" 3 nil nil
;;;WHAT IT DO
;Generates a tone with pitch pch and duration dur, with vibrato.
;coefs-string determines the timbre, by specifying how quickly harmonics should ;attenuate according to their frequency.
;coefs-string consists of terms delimited by parentheses (), whose elements may be ;either positive numbers or further sub-terms delimited by parentheses.
;If there are n terms, the first term gives the timbre at the start time, the second at ;dur/n, the third at 2*dur/n, and so on. Timbre is smoothly interpolated between these ;points.
;In a term with k elements, the rth element determines the amplitude of
;the r-1 mod k harmonics.
;If the rth element is a number x, then the ith harmonic (where i is r-1 modulo k) has ;amplitude i^(-x). That is, higher numbers make the harmonics attenuate faster.
;If the rth element is a subterm with l elements, then the sth element of that subterm
;determines the amplitude of the (r+k*(s-1)) mod k*l harmonics.
;As an example, here are the harmonics and relative amplitudes when
;coefs-string is "((5 (4 2)) 3)" and pch is 69:
;Harmonic Frequency Amplitude Amplitude
;1 440 1^-5 1.000000
;2 880 2^-3 0.125000
;3 1320 3^-4 0.012345
;4 1760 4^-3 0.015625
;5 2200 5^-5 0.000320
;6 2640 6^-3 0.004630
;7 3080 7^-2 0.020408
;8 3520 8^-3 0.001953
;;;ACKNOWLEDGEMENT
;Shout out to steve for a very thorough code review and loads of coding tips, and the ;rewritten gen-tables which I nicked verbatim from a post of his.
;Thank you David R. Sky for the handy string-to-list function.
;;;ADDITIONAL GLOBAL VARIABLES
;;Don't compute harmonics if their frequencies are too high.
(setq hi-freq (min (/ *sound-srate* 2) 17000.0))
(setq top-overtone (min
1000 ;magic number to avoid argument stack overflow when calling simrep.
(+ 1 (truncate (/ hi-freq (step-to-hz pch))))))
(defun get-coefficient (overtone lst divis)
;;;recursively search coefficient list for exponent
(let* ((len (length lst))
(branches (mult divis len))
(term (nth (rem (/ overtone divis) len) lst)))
(if (numberp term)
term
(get-coefficient overtone term branches))))
(defun get-amplitude (overtone coef-list)
(expt (+ 1.0 overtone)
(- (get-coefficient overtone coef-list 1))))
;2022-07-06, I fucked up my backup and started from this old version
;which still had stack overflow errors from simrep.
;(defun gen-table (coef-list)
; (simrep (current-overtone top-overtone)
; (scale (get-amplitude current-overtone coef-list)
; (build-harmonic (+ 1 current-overtone) 2048))))
;2022-07-06, this one should work better.
(defun gen-table (coef-list)
(do ((table 0)
(current-overtone 0))
((> current-overtone top-overtone) table)
(setq current-overtone (+ 1 current-overtone))
(setq table
(sum table (scale (get-amplitude current-overtone coef-list)
(build-harmonic (+ 1 current-overtone) 2048))))))
(defun normalize (sig)
;;;assume signal has 2048 samples
(scale (/ 0.75 (peak sig 2048)) sig))
(defun gen-tables (coef-lists pitch interval)
;;; Push wavetable and breakpoint onto a list 'tables'.
;;; Discard the final breakpoint, then return 'tables' for siosc.
(let ((count 1) tables)
(dolist (coef-list coef-lists (reverse (cdr tables)))
(push (normalize (gen-table coef-list)) tables)
(push (* interval count) tables)
(incf count))))
(defun modulation (vib-rate vib-depth duration)
(if (= vib-rate 0)
(s-rest duration)
(scale vib-depth (lfo vib-rate duration))))
(defun gen-tone (pitch duration coef-lists)
;;;get wavetables and mod. env., pass to built-in Nyquist oscillators.
(validate-coefs coef-lists)
(let* ((mod (modulation vib-rate vib-depth duration))
(interval (/ duration (length coef-lists)))
(tables (gen-tables coef-lists pitch interval)))
(if (= (length coef-lists) 1)
(fmosc pitch mod (maketable (car tables)) 0)
(siosc pitch mod tables))))
(defun validate-coefs (lst)
;;; All items in top level list must be lists.
(dolist (coef lst)
(when (not (listp coef))
(throw 'err (format nil "Coefficient ~s is invalid (not a list)." coef)))
(validate-items lst)))
(defun validate-items (item)
;;; Items must be lists or numbers. Test recursively.
(cond
((and (numberp item) (>= item 0))) ;valid
((listp item) (mapc 'validate-items item)) ;recurse
(t (throw 'err (format nil "Coefficient ~s is invalid." item)))))
;;;Took this one-liner from David R. Sky's Sequencer 2, released under GPLv2.
;;;Replace this with eval-string when 2.3.1 releases.
(defun string-to-list (string)
(read (make-string-input-stream (format nil "(~a)" string))))
;;read string, then generate tone (or throw error)
(setf coefs (string-to-list coefs-string))
(catch 'err (gen-tone pch dur coefs))