1$nyquist plugin
2$version 4
3$type process
4$mergeclips 1
5$restoresplits 0
6$name (_ "Crossfade Clips")
7$manpage "Crossfade_Clips"
8$action (_ "Crossfading...")
9$author (_ "Steve Daulton")
10$release 3.0.4
11$copyright (_ "GNU General Public License v2.0 or later")
12
13
14;; crossfadeclips.ny by Steve Daulton Dec 2014.
15
16;; Released under terms of the GNU General Public License v2.0 or later:
17;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
18;;
19;; For information about writing and modifying Nyquist plug-ins:
20;; https://wiki.audacityteam.org/wiki/Nyquist_Plug-ins_Reference
21
22;; Instructions:
23;; Place two audio clips into the same track.
24;; Select (approximately) the same amount of audio from the
25;; end of one clip and the start of the other.
26;; Apply the effect.
27;; The selected regions will be crossfaded.
28;;
29;; Note, the audio clips do not need to be touching. Any
30;; white-space between the clips is ignored.
31;;
32;; If the selected region is continuous audio (no splits),
33;; the the first and last halves of the selected audio
34;; will be crossfaded.
35;;
36;; Advanced Tip:
37;; A discontinuity in a waveform may be smoothed by applying
38;; a short crossfade across the glitch.
39
40;; Limitations (should not occur in normal usage).
41;; 1) There may be no more than two clips selected in each channel.
42;; 2) The selection may not start or end in white-space.
43
44
45(setf err1 (format nil (_ "Error.~%Invalid selection.~%More than 2 audio clips selected.")))
46(setf err2 (format nil (_ "Error.~%Invalid selection.~%Empty space at start/ end of the selection.")))
47
48
49(defun find-ends (T0 T1 clips)
50"Look for a split or gap within the selection, or return the mid-point"
51  (let ((trk-ends ())    ;starts of clips
52        (trk-starts ())) ;ends of clips
53    (dolist (clip clips)
54      ;; look for clip enclosing the selection.
55      (when (and (>= (second clip) T1) (<= (first clip) T0))
56        (psetq trk-ends   (list (/ (+ T0 T1) 2))
57               trk-starts (list (/ (+ T0 T1) 2)))
58        (return))
59      ;; look for track starts.
60      (when (and (> (first clip) T0) (< (first clip) T1))
61        (push (first clip) trk-starts))
62      ;; look for track ends.
63      (when (and (> (second clip) T0) (< (second clip) T1))
64        (push (second clip) trk-ends))
65      ; stop looking when we have passed end of selection.
66      (when (> (first clip) T1) (return)))
67    ;; if exactly one split position for crossfading,
68    ;; return clip positions, else error.
69    (cond
70      ((and (= (length trk-ends) 1)
71            (= (length trk-starts) 1)
72            (<= (car trk-ends) (car trk-starts)))
73        (list (car trk-ends)(car trk-starts)))
74      ((or (> (length trk-ends) 1)
75           (> (length trk-starts) 1))
76        (throw 'error err1))
77      (T (throw 'error err2)))))
78
79(defun crossfade (sig out-end in-start end)
80"Do the crossfade"
81  (abs-env
82    (control-srate-abs *sound-srate*
83      (let* ((fade-out (mult sig (env out-end 0)))
84             (cflen (max out-end (- end in-start))) ;crossfade length
85             (finstart (max (- out-end (- end in-start)) 0))
86             (fade-in (mult (extract (- end cflen) end sig)
87                            (env (- cflen finstart) 1 finstart))))
88        (sim fade-out fade-in)))))
89
90(defun env (dur direction &optional (offset 0))
91"Generate envelope for crossfade"
92  (abs-env
93    (if (< dur 0.01)            ;make it linear
94        (control-srate-abs *sound-srate*
95          (if (= direction 0)
96              (pwlv 1 dur 0)      ;fade out
97              (pwlv 0 offset 0 (+ offset dur) 1)))  ;fade in
98        (if (= direction 0)     ;cosine curve
99            (cos-curve dur 0)
100            (seq (s-rest offset)
101                 (cos-curve dur 1))))))
102
103(defun cos-curve (dur direction)
104"Generate cosine curve"
105  (if (= direction 0) ;fade out
106      (osc (hz-to-step (/ 0.25 dur)) dur *sine-table* 90)
107      (osc (hz-to-step (/ 0.25 dur)) dur *sine-table* 0)))
108
109(defun process (sig t0 t1 clips)
110"Find the split positions and crossfade"
111  (setf fadeclips
112    (multichan-expand #'find-ends t0 t1 clips))
113  (if (arrayp fadeclips)
114      (prog ((fade-out-end (min (first (aref fadeclips 0))
115                                (first (aref fadeclips 1))))
116             (fade-in-start (max (second (aref fadeclips 0))
117                                 (second (aref fadeclips 1)))))
118        (return
119          (multichan-expand #'crossfade sig
120                                       (- fade-out-end t0)
121                                       (- fade-in-start t0)
122                                       (- t1 t0))))
123      (crossfade sig
124                 (- (first fadeclips) t0)
125                 (- (second fadeclips) t0)
126                 (- t1 t0))))
127
128
129;;; Run the program.
130(if (= (length (get '*selection* 'tracks)) 1)
131    (catch 'error
132      (process *track*
133               (get '*selection* 'start)
134               (get '*selection* 'end)
135               (get '*track* 'clips)))
136    (format nil (_ "Error.~%Crossfade Clips may only be applied to one track.")))
137