133 lines
5.5 KiB
Plaintext
133 lines
5.5 KiB
Plaintext
|
;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))
|