1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-15 15:49:36 +02:00

Fix Bug 2519 - Noise Gate fails silently on longer selections

This commit is contained in:
SteveDaulton 2020-09-29 09:08:03 +01:00
parent 6584ae0d41
commit a48f8a40a8

View File

@ -3,93 +3,114 @@ $version 4
$type process $type process
$name (_ "Noise Gate") $name (_ "Noise Gate")
$manpage "Noise_Gate" $manpage "Noise_Gate"
$action (_ "Gating audio...")
$debugbutton false $debugbutton false
$preview enabled $preview enabled
$author (_ "Steve Daulton") $author (_ "Steve Daulton")
$release 2.4.0 $release 3.0.0
$copyright (_ "Released under terms of the GNU General Public License version 2") $copyright (_ "Released under terms of the GNU General Public License version 2 or later.")
;; Released under terms of the GNU General Public License version 2 or later:
;; Released under terms of the GNU General Public License version 2:
;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html . ;; http://www.gnu.org/licenses/old-licenses/gpl-2.0.html .
$control mode (_ "Select Function") choice (("Gate" (_ "Gate"))
$control mode (_ "Select Function") choice ((_ "Gate")
("Analyze" (_ "Analyse Noise Level"))) ("Analyze" (_ "Analyse Noise Level")))
0 0
$control stereo-link (_ "Stereo Linking") choice (("LinkStereo" (_ "Link Stereo Tracks")) $control stereo-link (_ "Stereo Linking") choice (("LinkStereo" (_ "Link Stereo Tracks"))
("DoNotLink" (_ "Don't Link Stereo"))) ("DoNotLink" (_ "Don't Link Stereo")))
0 0
$control low-cut (_ "Apply Low-Cut filter") choice ((_ "No") ;; Work around bug 2336 - Text after control is not read by screen reader.
("10Hz" (_ "10Hz 6dB/octave")) $control threshold (_ "Gate threshold (dB)") float "" -40 -96 -6
("20Hz" (_ "20Hz 6dB/octave")))
0
;; Work around bug 2336.
$control gate-freq (_ "Gate frequencies above (kHz)") float "" 0 0 10 $control gate-freq (_ "Gate frequencies above (kHz)") float "" 0 0 10
$control level-reduction (_ "Level reduction (dB)") float "" -12 -100 0 $control level-reduction (_ "Level reduction (dB)") float "" -24 -100 0
$control threshold (_ "Gate threshold (dB)") float "" -48 -96 -6 $control attack (_ "Attack (ms)") float "" 10 1 1000
$control attack (_ "Attack/Decay (ms)") float "" 250 10 1000 $control hold (_ "Hold (ms)") float "" 50 0 2000
$control decay (_ "Decay (ms)") float "" 100 10 4000
;; The gain envelope for the noisegate function may be a mono sound (stereo-link = 1, or *track* is mono)
;; or an array of sounds (stereo-link = 0 and *track* is stereo).
;; 'Level Reduction' is similar to "Range" or "Floor", but is a (negative) amount of gain
;; rather than a fixed level.
;;
;; To create the gain envelope:
;; 1. Resample to around 1000 Hz (bug 2519).
;; 2. If stereo track and stereo-link = 1, get the max of left and right.
;; 3. TRIGGER hold when level > threshold.
;; This adds a high level signal for 'hold' seconds when the level
;; falls below the threshold.
;; 4. Nyquist GATE function to generate exponential rise and decay.
;; Unlike analog noise gates, lookahead is used so that the gate
;; begins to open before the signal rises above the threshold, so
;; that when the threshold is reached, the gate is fully open.
;; This prevents the gate from clipping the beginning of words / sounds.
;; 5. Scale level of envelope and offset so that we have unity gain above
;; threshold, and 'level-reduction' below the threshold.
;; If silence-flag is set (= 1), gain below the threshold is zero.
; Global variables ; Global variables
(setq silence (if (> level-reduction -96) 0 1)) ; flag for silence (setf silence-flag (if (> level-reduction -96) 0 1))
(setq freq (* 1000.0 gate-freq)) (setf gate-freq (* 1000.0 gate-freq))
(setq level-reduction (db-to-linear level-reduction)) (setf floor (db-to-linear level-reduction))
(setq threshold (db-to-linear threshold)) (setf threshold (db-to-linear threshold))
(setq attack (/ attack 1000.0)) ;; As envelope is resampled to ~1000 Hz, the rise time will always be ~1 ms
(setq look attack) ; lookahead ;; longer than specified attack, so compensate here.
(setq decay attack) ; this could be replaced with a slider if required (setf attack (/ attack 1000.0))
(setf lookahead attack)
(setf attack (+ attack 0.001))
(setf decay (/ decay 1000.0))
(setf hold (/ hold 1000.0))
(defun error-check () (defun error-check ()
(when (>= freq (* *sound-srate* 0.45)) ;10% below Nyquist should be safe maximum. (let ((max-hz (* *sound-srate* 0.45)) ;10% below Nyquist should be safe maximum.
(max-khz (roundn (* 0.00045 *sound-srate*) 1))
(gate-freq-khz (roundn (/ gate-freq 1000.0) 1)))
(when (>= gate-freq max-hz)
;; Work around bug 2012. ;; Work around bug 2012.
(throw 'err (format nil (_ "Error. (throw 'err (format nil (_ "Error.
\"Gate frequencies above: ~s kHz\" \"Gate frequencies above: ~s kHz\"
is too high for selected track. is too high for selected track.
Set the control below ~a kHz.") Set the control below ~a kHz.")
gate-freq gate-freq-khz
(roundn (* 0.00045 *sound-srate*) 1)))) max-khz))))
(let ((start (get '*selection* 'start))
(end (get '*selection* 'end)))
(when (> (* *sound-srate* (- end start)) (1- (power 2 31)))
;; Work around bug 2012 and 439.
(throw 'err (format nil (_ "Error.
Selection too long.")))))
(when (< len 100) ;100 samples required (when (< len 100) ;100 samples required
;; Work around bug 2012. ;; Work around bug 2012.
(throw 'err (format nil (_ "~%Insufficient audio selected. (throw 'err (format nil (_ "Error.
Insufficient audio selected.
Make the selection longer than ~a ms.") Make the selection longer than ~a ms.")
(round-up (/ 100000 *sound-srate*)))))) (round-up (/ 100000 *sound-srate*))))))
;;; Analysis functions: ;;; Analysis functions:
;; Measure the peak level (dB) and suggest setting threshold a little higher.
(defun analyze (sig) (defun analyze (sig)
; Return analysis text. ; Return analysis text.
(let* ((test-length (truncate (min len (/ *sound-srate* 2.0)))) (let* ((test-length (truncate (min len (/ *sound-srate* 2.0))))
(levm (peak-avg-db sig test-length)) (peakdb (peak-db sig test-length))
(target (* 0.925 levm))) ;suggest 7.5% above noise level (target (+ 1.0 peakdb))) ;suggest 1 dB above noise level
;; Work around bug 2012. ;; Work around bug 2012.
(format nil (_ "Peak based on first ~a seconds ~a dB~% (format nil (_ "Peak based on first ~a seconds ~a dB~%
Suggested Threshold Setting ~a dB.") Suggested Threshold Setting ~a dB.")
(roundn (/ test-length *sound-srate*) 2) (roundn (/ test-length *sound-srate*) 2)
(roundn levm 2) (roundn peakdb 2)
(roundn target 0)))) (roundn target 0))))
(defun peak-avg-db (sig test-len) (defun peak-db (sig test-len)
;; Return average of positive/ negative peaks (dB). ;; Return absolute peak (dB).
;; For stereo tracks, return the maximum of the channels. ;; For stereo tracks, return the maximum of the channels.
(if (arrayp sig) (if (arrayp sig)
(let ((avgL (peak-avg (aref sig 0) test-len)) (let ((peakL (peak (aref sig 0) test-len))
(avgR (peak-avg (aref sig 1) test-len))) (peakR (peak (aref sig 1) test-len)))
(linear-to-db (max avgL avgR))) (linear-to-db (max peakL peakR)))
(let ((avg (peak-avg sig test-len))) (let ((peak (peak sig test-len)))
(linear-to-db avg)))) (linear-to-db peak))))
(defun peak-avg (sig test-len)
;; Return average of positive/ negative peaks.
;; We use the average for the analysis so as to ignore
;; DC offet (assuming noise is approximately symetrical).
(let* ((pos (s-max sig 0))
(neg (s-min sig 0))
(lev (peak pos test-len))
(inv-lev (peak neg test-len)))
(/ (+ lev inv-lev) 2.0)))
;;; Utility functions ;;; Utility functions
@ -109,48 +130,78 @@ Suggested Threshold Setting ~a dB.")
;;; Gate Functions ;;; Gate Functions
(defun noisegate (sig gatefollow lookahead risetime falltime floor threshold Hz)
(let ((gain (/ (- 1 (* silence floor)))) (defun noisegate (sig follower)
(env (get-env gatefollow lookahead risetime falltime floor threshold))) ;; Takes a sound and a peak follower envelope (a low sample rate sound) as arguments.
(if (> Hz 20) ;; Returns the gated audio.
(sim (mult (highpass8 sig Hz) gain env) (let ((gain (/ (- 1 (* silence-flag floor)))) ; silence-flag is 0 or 1.
(lowpass8 sig (* 0.91 Hz))) ;magic number 0.91 improves crossover. (env (get-env follower)))
(if (> gate-freq 20)
(let* ((high (highpass8 sig gate-freq))
(low (lowpass8 sig (* 0.91 gate-freq)))) ;magic number 0.91 improves crossover.
(sim (mult high gain env) low))
(mult sig gain env)))) (mult sig gain env))))
(defun get-env (follow look rise fall floor thresh)
(defun get-env (follow)
;; Return gate's envelope ;; Return gate's envelope
(let* ((gate-env (gate follow look rise fall floor thresh)) (let* ((gate-env (gate follow lookahead attack decay floor threshold))
(gate-env (clip gate-env 1.0))) ;gain must not exceed unity. (gate-env (clip gate-env 1.0))) ;gain must not exceed unity.
(diff gate-env (* silence floor)))) (diff gate-env (* silence-flag floor))))
(defun gate-follow (sig mode)
(defun gate (sound lookahead risetime falltime floor threshold)
;; This replaces Nyquist's GATE function. See bug 2519
;; Note: this does NOT perform the actual gating, it just generates an exponential
;; rise and decay for our "gain envelope".
(setf sound (snd-gate (seq (cue sound) (stretch-abs 1.0 (s-rest lookahead)))
lookahead risetime falltime floor threshold))
(snd-xform sound (snd-srate sound) (snd-t0 sound)
(+ (snd-t0 sound) lookahead) MAX-STOP-TIME 1.0))
(defun peak-follower (sig) ;;FOLLOW
;; Return signal that gate will follow. ;; Return signal that gate will follow.
(setf sig (hp sig 20)) ; hp filter to remove DC off-set ;; Reduce sample rate to around 1000 Hz to allow
(if (and (= mode 0)(arrayp sig)) ; stereo track set to mono/link ;; longer tracks to be processed (bug 2519).
(s-max (s-abs (aref sig 0)) (s-abs (aref sig 1))) (let ((step (round (/ *sound-srate* 1000))))
(s-abs sig))) (setf sig (multichan-expand #'snd-avg sig step step op-peak))
(when (and (arrayp sig)(= stereo-link 0))
(setf sig (s-max (aref sig 0) (aref sig 1))))
; SND-AVG looks for peak in next 'step', so shift envelope one sample earlier.
(setf sig (multichan-expand #'trim-first-sample sig))
(if (> hold 0)
(multichan-expand #'add-hold sig)
sig)))
(defun trim-first-sample (sig)
;; Remove the first sample from sig.
(let ((t0 (/ (snd-srate sig))))
(extract-abs t0 MAX-STOP-TIME (cue sig))))
(defun add-hold (sig)
;; Extend 'above threshold peaks' by 'hold' seconds.
;; TRIGGER acts when transitioning from less than or equal to zero to greater than zero,
;; which is not ideal, but it's a lot faster than looping through samples with Lisp.
;; We therefore need to invert the signal and offset vertically so that a fall across threshold
;; becomes a rise above zero.
(setf sig (mult -1 (diff sig threshold)))
(let ((holding (trigger sig (cue (hold sig hold)))))
(setf sig (sum threshold (mult -1 sig)))
(sum sig holding)))
(defun hold (sig dur)
;; generate 'hold' sound.
(control-srate-abs (snd-srate sig)
(stretch-abs 1.0 (const 1.0 dur))))
(defun hpfilter (sig)
;; First order filters to minimise ringing.
(case low-cut
(1 (hp sig 10))
(2 (hp sig 20))
(T sig)))
(defun process () (defun process ()
;; Main function
(error-check) (error-check)
(let ((sig (hpfilter *track*))) ;; 'peak-follower' may return a sound or array of sounds.
(setf *track* nil) (multichan-expand #' noisegate *track* (peak-follower *track*)))
(setf gatefollow (gate-follow sig stereo-link))
(multichan-expand #' noisegate sig
gatefollow
look
attack
decay
level-reduction
threshold
freq)))
;; Run program ;; Run program
(case mode (case mode