From b68d716e7304cfe8e97e7ac435d244cc6259d372 Mon Sep 17 00:00:00 2001 From: Steve Daulton Date: Tue, 2 Jun 2015 22:28:29 +0100 Subject: [PATCH] Add Limiter and Crossfade Clips effects. --- Makefile.am | 2 + plug-ins/crossfadeclips.ny | 130 +++++++++++++++++++++++++++++++++++ plug-ins/limiter.ny | 134 +++++++++++++++++++++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 plug-ins/crossfadeclips.ny create mode 100644 plug-ins/limiter.ny diff --git a/Makefile.am b/Makefile.am index 749d335dd..8b2d65f7d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -62,10 +62,12 @@ nobase_dist_pkgdata_DATA = \ plug-ins/beat.ny \ plug-ins/clicktrack.ny \ plug-ins/clipfix.ny \ + plug-ins/crossfadeclips.ny \ plug-ins/crossfadetracks.ny \ plug-ins/delay.ny \ plug-ins/equalabel.ny \ plug-ins/highpass.ny \ + plug-ins/limiter.ny \ plug-ins/lowpass.ny \ plug-ins/notch.ny \ plug-ins/pluck.ny \ diff --git a/plug-ins/crossfadeclips.ny b/plug-ins/crossfadeclips.ny new file mode 100644 index 000000000..6548f0e7d --- /dev/null +++ b/plug-ins/crossfadeclips.ny @@ -0,0 +1,130 @@ +;nyquist plugin +;version 4 +;type process +;mergeclips 1 +;restoresplits 0 +;name "Crossfade Clips" +;action "Crossfading..." +;author "Steve Daulton" +;copyright "Released under terms of the GNU General Public License version 2" + + +;; crossfadeclips.ny by Steve Daulton Dec 2014. +;; Released under terms of the GNU General Public License version 2: +;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + +;; Instructions: +;; Place two audio clips into the same track. +;; Select (approximately) the same amount of audio from the +;; end of one clip and the start of the other. +;; Apply the effect. +;; The selected regions will be crossfaded. +;; +;; Note, the audio clips do not need to be touching. Any +;; white-space between the clips is ignored. +;; +;; If the selected region is continuous audio (no splits), +;; the the first and last halves of the selected audio +;; will be crossfaded. +;; +;; Advanced Tip: +;; A discontinuity in a waveform may be smoothed by applying +;; a short crossfade across the glitch. + +;; Limitations (should not occur in normal usage). +;; 1) There may be no more than two clips selected in each channel. +;; 2) The selection may not start or end in white-space. + + +(setf err1 "Error.\nInvalid selection.\nMore than 2 audio clips selected.") +(setf err2 "Error.\nInvalid selection.\nEmpty space at start/ end of the selection.") + + +(defun find-ends (T0 T1 clips) +"Look for a split or gap within the selection, or return the mid-point" + (let ((trk-ends ()) ;starts of clips + (trk-starts ())) ;ends of clips + (dolist (clip clips) + ;; look for clip enclosing the selection. + (when (and (>= (second clip) T1) (<= (first clip) T0)) + (psetq trk-ends (list (/ (+ T0 T1) 2)) + trk-starts (list (/ (+ T0 T1) 2))) + (return)) + ;; look for track starts. + (when (and (> (first clip) T0) (< (first clip) T1)) + (push (first clip) trk-starts)) + ;; look for track ends. + (when (and (> (second clip) T0) (< (second clip) T1)) + (push (second clip) trk-ends)) + ; stop looking when we have passed end of selection. + (when (> (first clip) T1) (return))) + ;; if exactly one split position for crossfading, + ;; return clip positions, else error. + (cond + ((and (= (length trk-ends) 1) + (= (length trk-starts) 1) + (<= (car trk-ends) (car trk-starts))) + (list (car trk-ends)(car trk-starts))) + ((or (> (length trk-ends) 1) + (> (length trk-starts) 1)) + (throw 'error err1)) + (T (throw 'error err2))))) + +(defun crossfade (sig out-end in-start end) +"Do the crossfade" + (abs-env + (control-srate-abs *sound-srate* + (let* ((fade-out (mult sig (env out-end 0))) + (cflen (max out-end (- end in-start))) ;crossfade length + (finstart (max (- out-end (- end in-start)) 0)) + (fade-in (mult (extract (- end cflen) end sig) + (env (- cflen finstart) 1 finstart)))) + (sim fade-out fade-in))))) + +(defun env (dur direction &optional (offset 0)) +"Generate envelope for crossfade" + (abs-env + (if (< dur 0.01) ;make it linear + (control-srate-abs *sound-srate* + (if (= direction 0) + (pwlv 1 dur 0) ;fade out + (pwlv 0 offset 0 (+ offset dur) 1))) ;fade in + (if (= direction 0) ;cosine curve + (cos-curve dur 0) + (seq (s-rest offset) + (cos-curve dur 1)))))) + +(defun cos-curve (dur direction) +"Generate cosine curve" + (if (= direction 0) ;fade out + (osc (hz-to-step (/ 0.25 dur)) dur *sine-table* 90) + (osc (hz-to-step (/ 0.25 dur)) dur *sine-table* 0))) + +(defun process (sig t0 t1 clips) +"Find the split positions and crossfade" + (setf fadeclips + (multichan-expand #'find-ends t0 t1 clips)) + (if (arrayp fadeclips) + (prog ((fade-out-end (min (first (aref fadeclips 0)) + (first (aref fadeclips 1)))) + (fade-in-start (max (second (aref fadeclips 0)) + (second (aref fadeclips 1))))) + (return + (multichan-expand #'crossfade sig + (- fade-out-end t0) + (- fade-in-start t0) + (- t1 t0)))) + (crossfade sig + (- (first fadeclips) t0) + (- (second fadeclips) t0) + (- t1 t0)))) + + +;;; Run the program. +(if (= (length (get '*selection* 'tracks)) 1) + (catch 'error + (process *track* + (get '*selection* 'start) + (get '*selection* 'end) + (get '*track* 'clips))) + "Error.\nCrossfade Clips may only be applied to one track.") diff --git a/plug-ins/limiter.ny b/plug-ins/limiter.ny new file mode 100644 index 000000000..4d0d2a655 --- /dev/null +++ b/plug-ins/limiter.ny @@ -0,0 +1,134 @@ +;nyquist plug-in +;version 4 +;type process +;categories "http://lv2plug.in/ns/lv2core/#DynamicsPlugin" +;name "Limiter..." +;action "Limiting..." +;preview enabled +;author "Steve Daulton" +;copyright "Released under terms of the GNU General Public License version 2" + +;; limiter.ny by Steve Daulton November 2011, updated May 2015. +;; 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: +;; http://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference + +;control type "Type" choice "Soft Limit,Hard Limit,Soft Clip,Hard Clip" 0 +;control gain-L "Input Gain (dB)\nmono/Left" real "" 0 0 10 +;control gain-R "Input Gain (dB)\nRight channel" real "" 0 0 10 +;control thresh "Limit to (dB)" real "" -3 -10 0 +;control hold "Hold (ms)" real "" 10 1 50 +;control makeup "Apply Make-up Gain" choice "No,Yes" 0 + +(if (not (boundp 'type)) + (setf type 0)) + +(if (boundp 'gain-L) + (setf gain-L (db-to-linear gain-L)) + (setf gain-L 1)) + +(if (boundp 'gain-R) + (setf gain-R (db-to-linear gain-R)) + (setf gain-R 1)) + +(if (boundp 'thresh) + (setf thresh (db-to-linear thresh)) + (setf thresh (db-to-linear -3.0))) + +(if (not (boundp 'hold)) + (setf hold 10.0)) + +(if (boundp 'makeup) + (if (= makeup 1) (setf makeup T) (setf makeup nil)) + (setf makeup nil)) + +;;; brick wall limiter +(defun hardlimit (sig limit hld) + (let* ((time (/ hld 3000.0)) ; lookahead time (seconds) + (samples (round (* time *sound-srate*))) ; lookahead in samples + (peak-env (get-env sig samples time limit))) + (mult sig + (snd-exp + (mult -1 (snd-log peak-env)))))) + +;;; Envelope follower for brick wall limiter +(defun get-env (sig step lookahead limit) + (let* ((sig (mult (/ limit) sig)) + (pad-time (* 3 lookahead)) ; padding required at start (seconds) + (pad-s (* 3 step)) ; padding smaples + (padding (snd-const (peak sig pad-s) 0 *sound-srate* pad-time)) + (peak-env (snd-avg sig (* 4 step) step OP-PEAK))) + (extract 0 1 + (s-max 1 + (sim padding + (at-abs pad-time (cue peak-env))))))) + +(defun softlimit (sig thresh hld) + (let* ((sig (hardlimit sig 1 hold)) + (step (truncate (* (/ hld 3000.0) *sound-srate*))) + (waveshape (snd-avg sig (* 4 step) step op-peak)) + (env (sum thresh (mult thresh (diff 1 waveshape)))) + (env (clip env 1)) + (offset (/ (* 3 step) *sound-srate*)) + (initial (peak sig (* 2 step))) + (pause-lev (sum thresh (mult thresh (diff 1 initial)))) + (pause-lev (clip pause-lev 0.9)) + (pause (snd-const pause-lev 0 *sound-srate* offset))) + (setf env + (sim + pause + (at-abs offset (cue env)))) + (mult sig env))) + + +(defun soft-clip-table () +"Lookup table for soft clipping wave-shaper" + (abs-env + (Control-srate-abs *sound-srate* + (let* ((knee (- 1 (/ 1.0 pi))) + (kcurve (mult knee (osc (hz-to-step (/ (* 4 knee))) knee))) + (ikcurve (mult knee (osc (hz-to-step (/ (* 4 knee))) knee *sine-table* -90))) + (lin (pwlv -0.5 knee -0.5 + (+ knee (/ 2.0 pi)) 0.5 + 2.0 0.5 + 2.0 (+ 0.5 knee) + 2.1 (+ 0.5 knee)))) + (mult (/ 2.0 pi) + (sim + (at-abs 0 (cue ikcurve)) + (at-abs 0 (cue lin)) + (at-abs (+ knee (/ 2.0 pi)) (cue kcurve)))))))) + +(defun soft-clip (sig) + (let* ((knee (- 1 (/ 1.0 pi))) + (clip-level (* (+ 0.5 knee)(/ 2.0 pi))) + (sig (mult clip-level (/ thresh) sig))) + (if makeup + (mult 0.999 + (/ clip-level) + (shape sig (soft-clip-table) 1.0)) + (mult thresh + (/ clip-level) + (shape sig (soft-clip-table) 1.0))))) + + +(defun makupgain (sig) + (if makeup + (mult (/ 0.999 thresh) sig) ;keep below 0dB + sig)) + +;; Pre-gain +(setf *track* + (if (arrayp *track*) + (vector (mult (aref *track* 0) gain-L) + (mult (aref *track* 1) gain-R)) + (mult *track* gain-L))) + + +(case type + (0 (makupgain (multichan-expand #'softlimit *track* thresh hold))) + (1 (makupgain (multichan-expand #'hardlimit *track* thresh hold))) + (2 (soft-clip *track*)) + (T (makupgain (clip *track* thresh))))