Bug 2352: Rewrite of Silence Finder

Silence Finder now throws an error if selection too long.
UI updated to use negative dB consistent with all other shipped effects.
Use max of channels for stereo tracks rather than sum of channels.
Avoid resampling track twice.
Update to version 4 syntax.
Remove Debug button, consistent with other shipped effects.
This commit is contained in:
SteveDaulton 2020-03-11 19:13:55 +00:00
parent ac37d78388
commit 1f8574c144
1 changed files with 72 additions and 101 deletions

View File

@ -1,124 +1,95 @@
$nyquist plug-in
$version 1
$version 4
$type analyze
$debugbutton false
$debugflags trace
$name (_ "Silence Finder")
$manpage "Silence_Finder"
$action (_ "Finding silence...")
$author (_ "Alex S. Brown")
$release 2.3.0
$author (_ "Steve Daulton")
$release 2.4.0
$copyright (_ "Released under terms of the GNU General Public License version 2")
;; by Alex S. Brown, PMP (http://www.alexsbrown.com)
;; Original version by Alex S. Brown, PMP (http://www.alexsbrown.com)
;;
;; Adds point labels in areas of silence according to the specified
;; level and duration of silence. If too many silences are detected,
;; increase the silence level and duration; if too few are detected,
;; reduce the level and duration."
;; 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
$control sil-lev (_ "Treat audio below this level as silence [ -dB]") real "" 26 0 100
$control sil-dur (_ "Minimum duration of silence [seconds]") real "" 1.0 0.1 5.0
$control labelbeforedur (_ "Label placement [seconds before silence ends]") real "" 0.3 0.0 1.0
$control threshold (_ "Treat audio below this level as silence (dB)") real "" -30 -100 0
$control min-silence (_ "Minimum duration of silence (seconds)") real "" 1.0 0.1 5.0
$control label-position (_ "Label placement (seconds before silence ends)") real "" 0.3 0.0 1.0
;Create a function to make the sum the two channels if they are stereo
(defun mono-s (s-in) (if (arrayp s-in) (snd-add (aref s-in 0) (aref s-in 1))
s-in))
(setf threshold (db-to-linear threshold))
; Label is usually offset to earlier time.
(setf label-position (- label-position))
;Create a function to reduce the sample rate and prepare the signal for
;analysis. RMS is good to monitor volume the way humans hear it, but is not
;available in Audacity. Used a peak-calculating function instead.
;NOTE: this is the place to add any processing to improve the quality of the
;signal. Noise filters could improve the quality of matches for noisy signals.
;PERFORMANCE vs. ACCURACY
;Reducing the samples per second should improve the performance and decrease
;the accuracy of the labels. Increasing the samples per second will do the
;opposite. The more samples checked, the longer it takes. The more samples
;checked, the more precisely the program can place the silence labels.
;my-srate-ratio determines the number of samples in my-s. Set the number after (snd-srate s)
;higher to increase the number of samples.
;i18n-hint: Abbreviation of "Silence".
(setf *labeltxt* (_ "S"))
(setf *labels* NIL)
(defun my-s (s-in)
(setq my-srate-ratio (truncate (/ (snd-srate (mono-s s-in)) 100)))
(snd-avg (mono-s s-in) my-srate-ratio my-srate-ratio OP-PEAK)
)
;Set the silence threshold level (convert it to a linear form)
(setq thres (db-to-linear (* -1 sil-lev)))
;Store the sample rate of the sound
(setq s1-srate (snd-srate (my-s s)))
;Initialize the variable that will hold the length of the sound.
;Do not calculate it now with snd-length, because it would waste memory.
;We will calculate it later.
(setq s1-length 0)
;Initialize the silence counter and the labels variable
(setq sil-c 0)
(setq l NIL)
;Convert the silence duration in seconds to a length in samples
(setq sil-length (* sil-dur s1-srate))
(defun to-mono (sig)
;;; coerce sig to mono.
(if (arrayp sig)
(s-max (s-abs (aref sig 0))
(s-abs (aref sig 1)))
sig))
;Define a function to add new items to the list of labels
(defun add-label (l-time l-text)
(setq l (cons (list l-time l-text) l))
)
(defun reduce-srate (sig)
;;; Reduce sample rate to (about) 100 Hz.
(let ((ratio (round (/ *sound-srate* 100))))
(snd-avg sig ratio ratio OP-PEAK)))
;The main working part of the program, it counts
;the number of sequential samples with volume under
;the threshold. It adds to a list of markers ever time
;there is a longer period of silence than the silence
;duration amount.
(defun add-label (samples srate offset)
;;; Add new label to *labels*
(let ((time (+ (/ samples srate) offset)))
(push (list time *labeltxt*) *labels*)))
;It runs through a loop, adding to the list of markers (l)
;each time it finds silence.
(let (s1) ;Define s1 as a local variable to allow efficient memory use
; Get the sample into s1, then free s to save memory
(setq s1 (my-s s))
(setq s nil)
;Capture the result of this "do" loop, because we need the sountd's legnth
;in samples.
(setq s1-length
;Keep repeating, incrementing the counter and getting another sample
;each time through the loop.
(do ((n 1 (+ n 1)) (v (snd-fetch s1) (setq v (snd-fetch s1))))
;Exit when we run out of samples (v is nil) and return the number of
;samples processed (n)
((not v) n)
;Start the execution part of the do loop
;if found silence, increment the silence counter
(if (< v thres) (setq sil-c (+ sil-c 1)))
(defun format-time (s)
;;; format time in seconds as h m s.
(let* ((hh (truncate (/ s 3600)))
(mm (truncate (/ s 60))))
;i18n-hint: hours minutes and seconds. Do not translate "~a".
(format nil (_ "~ah ~am ~as")
hh (- mm (* hh 60)) (rem (truncate s) 60))))
;If this sample is NOT silent and the previous samples were silent
;then mark the passage.
(if (and (> v thres) (> sil-c sil-length))
;Mark the user-set number of seconds BEFORE this point to avoid clipping the start
;of the material.
; Should "S" be translated or become a control value?
(add-label (- (/ n s1-srate) labelbeforedur) "S")
)
;If this sample is NOT silent, then reset the silence counter
(if (> v thres)
(setq sil-c 0)
)
)
)
)
(defun label-silences (sig)
;;; Label silences that are longer than 'min-len' samples.
(let* ((sample-count 0)
(sil-count 0)
(srate (snd-srate sig))
(min-len (* min-silence srate)))
(do ((val (snd-fetch sig) (snd-fetch sig)))
((not val) sil-count)
(incf sample-count)
(cond
((< val threshold)
(setf sil-count (1+ sil-count)))
(t (when (> sil-count min-len)
(add-label sample-count srate label-position))
(setf sil-count 0))))
;; If long trailing silence, add final label at 'min-silence' AFTER last sound.
(when (> sil-count min-len)
(setf final-silence (- sample-count sil-count))
(add-label final-silence srate min-silence))
*labels*))
;Check for a long period of silence at the end
;of the sample. If so, then mark it.
(if (> sil-c sil-length)
;If found, add a label
;Label time is the time the silence began plus the silence duration target
;amount. We calculate the time the silence began as the end-time minus the
;final value of the silence counter
(add-label (+ (/ (- s1-length sil-c) s1-srate) sil-dur) "S")
)
;If no silence markers were found, return a message
(if (null l)
(setq l (format nil (_ "No silences found. Try reducing the silence~%level and minimum silence duration.")))
)
l
;; Bug 2352: Throw error if selection too long for Nyquist.
(let* ((dur (- (get '*selection* 'end)
(get '*selection* 'start)))
(samples (* dur *sound-srate*))
(max-samples (1- (power 2 31))))
(if (>= samples max-samples)
(format nil "Error.~%Selection must be less than ~a."
(format-time (/ max-samples *sound-srate*)))
;; Selection OK, so run the analyzer.
(let ((sig (reduce-srate (to-mono *track*))))
(setf *track* nil) ;free *track* from memory
(if (label-silences sig)
*labels*
(_ "No silences found.
Try reducing the silence level and
the minimum silence duration.")))))