audacia/plug-ins/crossfadeclips.ny

137 lines
4.9 KiB
Common Lisp

$nyquist plugin
$version 4
$type process
$mergeclips 1
$restoresplits 0
$name (_ "Crossfade Clips")
$manpage "Crossfade_Clips"
$action (_ "Crossfading...")
$author (_ "Steve Daulton")
$release 2.3.0
$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
;;
;; For information about writing and modifying Nyquist plug-ins:
;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
;; 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 (format nil (_ "Error.~%Invalid selection.~%More than 2 audio clips selected.")))
(setf err2 (format nil (_ "Error.~%Invalid selection.~%Empty 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)))
(format nil (_ "Error.~%Crossfade Clips may only be applied to one track.")))