The basic technique to “Normalize” a sound, is to find the peak level, then scale the sound by an appropriate multiple of the inverse of that value.
For example, to normalize a mono track to 0 dB (0 dB has a linear value of 1.0)
(setq peak-s (peak s NY:ALL))
(scale (/ 1.0 peak-s) s)
The problem with this is that (peak) reads “s” into RAM memory in order to calculate the peak value. If the selection being processed (“s”) is very large, this can exhaust the system memory and cause Audacity to crash very badly. Even if Audacity does not crash, the system may need to pass data from RAM to the hard drive (swap file) which will cause Audacity to become extremely slow and unresponsive to the degree that it appears to have frozen.
The most simple way to avoid this is to limit the number of samples that are read.
NY:ALL is a pre-initialised variable with a value of 1000000000 (1 billion).
This is a big enough number to cope with very long track selections, but depending on the sample format and the amount of available RAM may allow more samples to be read than can be held in RAM.
To protect against running out of RAM, a smaller number could be used, for example;
(setq bignum 1000000)
(setq peak-s (peak s bignum))
(scale (/ 1.0 peak-s) s)
In this case, only the first “bignum” (1 million) samples will be read.
This is fine as long as either the selection is less than 1 million samples duration, or, the maximum peak occurs within the first 1 million samples.
Roger Dannenberg has suggested a workaround that allows long tracks to be normalized (full details here: http://www.cs.cmu.edu/~music/nyquist/debug-plugin.html
The solution is to first calculate the peak value, but set “s” to nil to avoid retaining samples:
;; Get the input signal s, but set s to nil to avoid retaining samples.
(defun get-signal ()
(let ((local-s s))
(setf s nil)
local-s))
;; Compute the peak value of s
(setf peak-value (peak (get-signal) ny:all))
(gc)
;; Save the peak-value on *scratch* for later use.
;; Assume that the effect name (normalize-part1) can be safely used
;; as a property symbol without any conflicts.
(putprop '*scratch* peak-value 'normalize-part1)
;; Return a string:
(format nil "The peak value is: ~A" peak-value)
Because “s” has been set to “nil” the sound passed from Audacity to Nyquist no longer exists, so it is not possible to normalize the sound at this time.
However because we have saved the value as a property of scratch, we can run a second plug-in to read the value and apply the normalization:
(setf peak-value (get '*scratch* 'normalize-part1))
(cond ((floatp peak-value)
(mult s (/ (db-to-linear db) peak-value)))
(t
"No peak-value found -- run normalize-part1 to compute the peak value"))
Two important issues here:
- Many existing Audacity Nyquist plug-ins will cause Audacity to freeze or crash if long selections are processed due to normalizing and running out of RAM.
- The current workaround in Audacity is to use a two pass approach, which is very inconvenient for both plug-in authors and (more importantly) for users.
For users of stand-alone Nyquist there are other approaches that may be used and are described here: http://www.audacity-forum.de/download/edgar/nyquist/nyquist-doc/manual/part5.html#36 These methods are not applicable to the problem in Audacity which is specifically about normalizing the sound “s”.
In the longer term I think that the problem of Normalizing needs to be achieved through the Audacity implementation of Nyquist.
Currently, the sound from a track is passed to Nyquist in the global variable “s”.
The length of “s” (in samples) is set as the global “LEN”.
My proposal is that the peak value is also passed to Nyquist, either as a global variable, or as a property of “s”.
I think the latter solution is more elegant and offers opportunities of further development, for example moving the value of LEN to a property of “s”, or adding the track name as a property, or perhaps even passing envelope points to Nyquist as an array that is a property of “s”.
(see also https://forum.audacityteam.org/t/re-audacity-nyquist-wish-list-peak-level/15152/1 )
A more versatile and comprehensive solution would be an s-read replacement that can read the relevant audio segment from audacity as suggested by Richard Ash http://audacity.238276.n2.nabble.com/Memory-bug-with-Nyquist-in-Audacity-td257313.html#a257316 but it sounds like this would be very difficult to implement.