About the code…
- Use spaces not tabs
- Nyquist / Xlisp is a “garbage collected” language. Memory management is automatic, and when working with small data objects the developer can usually leave this entirely up to Nyquist to deal with, but when working with very large data objects (such as long sounds) it is necessary to have at least some idea about what is going on. This is a rather “advanced” topic, but here’s a very brief account:
When a program runs, the data that is being operated on is allocated space in the computer’s RAM memory. When that data expires, the garbage collector (part of Nyquist) will attempt to free the memory, deleting the obsolete data so that the memory space may be used again.
When working with long sounds, a well designed program allows Nyquist to free up memory as it goes.
For example (simplified model):
Say we have an input sound “TRACK” which I’ll represent as lowercase letters:
abcdefghijklmnopqrstuvwxyz
and the code processes the sound and outputs a result which I’ll represent as uppercase letters:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
If possible, Nyquist will work in this fashion:
read: a
a -> A
write: A
garbage collect: a and A
read: b
b -> B
write: B
garbage collect: b and B
read: c
c -> C
write: C
garbage collection
...
Because Nyquist is operating on blocks of samples, and is able to garbage collect the used blocks as it goes, it can work with extremely long sounds without ever running out of memory.
Now let’s look at another example - this one is a classic example of when Nyquist’s garbage collection can’t do it’s job.
Find the peak level, then amplify the track so that the new peak level has a specified value (“normalization”).
In this case, Nyquist must search the entire TRACK data to find the peak value, but cannot garbage collect because it needs to go back and multiply each sample by a value.
(setf target-val 1.0)
(setf absolute-peak (peak *track* ny:all)) ; Find the peak level
;; *track* data must still exist, so must not be garbage collected
(mult *track* (/ target-val absolute-peak))
I’m not sure if it’s possible to “fix” your code to allow garbage collection. (Nyquist does have some advanced techniques for this kind of job, but that’s too much for a forum post).
- “DRY” (Don’t Repeat Yourself)
Notice that your code repeats something very similar to this, 4 times:
(setf sig (highpass8 sigtrack 450))
(setf sig (lowpass8 sig 1500))
(setf gatefollow (gate-follow sig))
(setf reduce (db-to-linear (+ reduction 2_band_R_offset)))
(setf threshold (db-to-linear (+ (+ (get-rms sig (truncate len)) sensitivity) 2_band_T_offset)))
(setf 2-bandgated (multichan-expand #' noisegate sig gatefollow look attack release reduce threshold))
(setf msg (format nil
"~a~%Low-mid: Reduce: ~a; Threshold: ~a"
msg (linear-to-db reduce) (linear-to-db threshold)))
Better to extract that out from your main function into a separate function, so that it can then be reused as many times as you want.
If you make the parameters for each band into a list, your “main function” could be reduced to something like this:
(defun process (param-list)
(error-check)
(let ((sigtrack *track*)
(ln (truncate len))
(output 0))
(setf *track* nil)
(dolist (params param-list output)
(setf output (sum output
(process-band sig ln params))))))
Note also that calculations such as “(truncate len)” only need to be done once, whereas your code calculates it multiple times.
(in the specific case of “(truncate len)”, the cost of repeating the calculation is insignificant, but in other situations repetition can be costly in terms of performance and memory use.)