;nyquist plug-in
;version 3
;type process
;categories "http://lv2plug.in/ns/lv2core#UtilityPlugin"
;name "Vocal Remover 13d (for center-panned vocals)..."
;action "Removing vocals or other center-panned audio..."
;info "by David R. Sky www.shellworld.net/~davidsky/ with help from Gale Andrews \nReleased under terms of the GNU General Public License version 2 \n\nRemoves part or all of center-panned audio in a stereo track \nby inverting one channel then panning both channels to center. \n\nFor more complex removal options, please see one of the help \nscreens which can be viewed by selecting an item in the first\ndrop-down menu below. \nAfter viewing help, it is necessary to re-open Vocal Remover."

;control action "Action" choice "Remove vocals, View general Help 1, View general help 2, View vocal ranges" 0
;control removal-choice "Remove" choice "Entire spectrum, Inside vocal range, Outside vocal range" 0
;control range-choice "Vocal range choice" choice "My vocal range (below), Bass 80 320, Baritone 100 440, Tenor 120 560, Alto 150 650, Mezzo 200 850, Soprano 250 1100" 1
;control my-range "My vocal range [lower and upper frequencies]" string "2 values, 0 - 20000 (hz)" "250 750"
;control reduction "Vocal reduction amount" int "1 - 100%" 100 1 100
;control norm-level "Maximum output volume" int "1 - 100% (0=no adjust)" 95 0 100

; Center pan Remover by David R. Sky November 12, 2004
; updated October/November 2007. 
; Further modified by Gale Andrews January 2008 to make 
; full spectrum removal the default, restore a single Help 
; screen and restore error checking.
; Updated by DRS January 2009:
; added reduction amount so it's now possible to retain some degree 
; of center-panned audio; 
; when normalize level is 0, enable vocal remover 
; to perform effect without normalizing;
; improved error-checking;
; and Gale converted the wikipedia image listing of vocal ranges into text.
; further refining by DRS Jul-Aug 2009:
; corrected normalization code, improved error checking,
; formatted text so it's easier to read in Ubuntu.

; Thanks to David Hostetler, for notes in his own vocal remover plug-in,
; which makes this plug-in simpler. 
; Thanks to Gale Andrews, who found the vocal ranges visual list 
; in Wikipedia, and who translated same into text for me.
; Thanks to Steve the fiddle Daulton, whose email exchange helped me
; realize it was irrelevant to prepare for all possible error situations!
; and who found an important bug -
; entering certain erroneous characters triggered an error 
; in previous My User Range field - fixed.


(cond ; display one of the help screens, or apply VR
((= action 1) ; display general Help screen 1
(format nil  
"Vocal Remover General help - 1 of 2\n\n Works best on a lossless format file such as .wav or .aif,\nnot on a lossy format file such as .mp3. You'll need to use\na program such as CD-ex to rip a song from a CD before using\nVocal Remover(VR).\n\n Any audio including vocals can only be removed or reduced\nwith VR when panned to center (equally loud in both\nchannels) - typical for vocals and drums. The further that\naudio is panned away from center, the more of it remains\nafter VR has been applied.\n\n Inverting one channel then moving (panning) both channels to\ncenter removes center-panned audio (audio common to both\nchannels), which is the 'Remove: entire spectrum' option.
"))


((= action 2) ; view general help 2
(format nil 
"Vocal Remover General help - 2 of 2\n\n Instead of full-spectrum removal, you may want to remove (or\nkeep) part of the center-panned audio, in which case you can\nuse a combination of 'Remove: Inside (or outside) Vocal\nrange' and a selected vocal range, from bass to soprano. You\ncan also choose 'My Vocal range', in which case you need to\nspecify two frequency values (both between 0 and 20000)\ninside the 'My Vocal range lower and upper frequencies'\nfield. Your selected range will be used to remove\ncenter-panned audio. And, the value you set in 'Vocal\nreduction amount' determines how much of your specified\nvocal range to remove.\n\n Finally, the 'Maximum output volume' setting gives you\n'room' to later mix in your own vocals - karioke!
"))


((= action 3) ; display vocal ranges screen
(format nil 
"Vocal Remover - pre-programmed vocal ranges\n\n If you've selected 'Remove: inside (or outside)' a vocal\nrange, you can select from 1 of 6 pre-programmed vocal\nranges -\n\n Bass 80 320\nBaritone 100 440\nTenor 120 560\nAlto 150 650\nMezzo 200 850\nSoprano 250 1100\n\n If you select 'Vocal range choice: My vocal range', enter\ntwo frequency values (both between 0 and 20000) into the 'My\nVocal band lower and upper frequencies' field, such as the\ndefault values\n\n 250 750\n\n And, the value you set in 'Vocal reduction amount'\ndetermines how much of your specified vocal range to\nremove.\n\n Thanks to Gale Andrews for the above Wikipedia list.
"))


(t ; apply vocalremover
(defun string-to-list (string)
(read (make-string-input-stream (format nil "(~a)" string))))


; function to check char: is it a number or space?
; returns t if valid, nil otherwise
(defun check4numbers (string i)
(setf char-code (char-code (char string i)))
(if (or 
(= char-code 32) ; a space character
(and (>= char-code 48) ; between the chars 0...
(<= char-code 57)) ; ...through 9
) ; end or
t nil))


; determine if chars in string are numeric chars
; returns list of valid-number string, error count
(defun clean-numbers-string (string)
(setf temp-string "") ; initialize empty temp string 
(setf string-errors 0) ; initialize string-errors
;
(dotimes (i (length string))
(progn
(setf temp-string (if 
(check4numbers string i) ; if char meets criteria of number or space 
(strcat temp-string (subseq string i (1+ i)))
temp-string) ; end if
) ; end setf temp-string
;
(setf string-errors (if (check4numbers string i)
string-errors (1+ string-errors))) ; end setf string-errors
)) ; end progn, dotimes
; return list of temp-string, string-errors:
(list temp-string string-errors))


(setf range (if (> removal-choice 0)
(nth  range-choice (list 
(string-to-list (first (clean-numbers-string my-range))) ; my vocal range
(list 80 320) ; bass range
(list 100 440) ; baritone range
(list 120 560) ; tenor range
(list 150 650) ; alto range
(list 200 850) ; mezzo range
(list 250 1100) ; soprano range
)))) ; end setf


; function to test that value is within lower and upper limits -
; if yes returns t, nil otherwise
(defun inside-range (value lower upper)
(if 
(and (>= value lower) (<= value upper))
t nil))


; function to check that my-range has two numeric values
; within proper range -
; if so return t, nil otherwise ??
(defun test-my-range (range)
(if (and 
(> (length range) 1)
(numberp (first range))
(inside-range (first range) 0 20000)
(numberp (second range))
(inside-range (second range) 0 20000))
t  nil))


; initialize empty error-msg string
(setf error-msg "")


; Beginning of error-checking
;
; check that selected audio is stereo
(setf error-msg (if (arrayp s)
error-msg
(strcat error-msg (format nil 
"Vocal Remover requires an unsplit, stereo track.\n\n If you have a stereo track split into separate left and\nright channels, use 'Make Stereo Track' from the 'Track'\nDrop-down Menu,then run Vocal Remover again.\n\n
"))))


; If range-choice is 0 My vocal range, and removal choice > 0,
; check there are two valid frequencies in the My Vocal Range field.
(setf error-msg (if
(and (> removal-choice 0) ; remove inside or outside range
(= range-choice 0) ; My Vocal Range
(not (test-my-range range)))
(strcat error-msg (format nil 
"Ignoring any (parentheses) and ''quote marks'', you have\nentered\n\n ~a\n\n into the 'My Vocal Range' field, which you selected from the\n'Vocal Range Choice' menu.\n\n Please enter two different numeric values (both between 0\nand 20000) into that field, such as the default values\n\n 250 750\n\n
have\nentered\n\n~a\n\nfor your vocal frequency range.\n\nPlease
enter 2 different separate lower and upper numeric\nfrequency
values for the vocal range you wish to remove or\nretain, such as
the default values\n\n250 750\n\nBoth values should be between 0
and 20000 inclusive.
" my-range)) error-msg))


; function to get peak amplitude for normalization.
; it does not work without the 'arrayp signal' 
; check.
(defun my-peak (signal)
(if (arrayp signal) 
(max (peak (aref signal 0) 8000000)
(peak (aref signal 1) 8000000))
(peak signal 8000000)))


; function to normalize signal
(defun normalize (norm-level signal)
(cond ((= norm-level 0)
(cue signal)) ; end norm-level 0 - do not normalize
;
(t ; perform normalization
; give an error message if output would be silence
(if (= (my-peak signal) 0)
(format nil 
"Error -\n\n Your Vocal Remover settings have resulted in silence. This\nwould happen if you started with an audio track that was\nidentical in both left and right channels (sounding mono)\nand you selected the 'Remove: entire spectrum' option.\n\n Please select 'Remove: inside (or outside) Vocal range', and\neither choose one of the 6 pre-programmed vocal ranges, or\nselect 'My Vocal range' and enter 2 different frequency\nvalues (both between 0 and 20000) into the 'My Vocal range'\nfield.
")
;
(scale (/ norm-level (my-peak signal)) signal)) ; end if
)))


; generate error message _or_ perform effect
(cond
((> (length error-msg) 0)
(format nil 
"Error -\n\n~a
" error-msg))
;
(t ; no error msg, perform removal effect
;
; if retain/remove frequency band is selected, 
; sort lower and upper range values to true lower and upper values 
; - "just in case"
(if (> removal-choice 0)
(progn (setf lower (min (first range) (second range)))
(setf upper (max (first range) (second range)))))
;
; convert reduction percent integer to real value
(setf reduction (* 
(min 100.0 (max 1.0 (float reduction)))
-0.01))
;
; perform vocalremover effect.
; Normalization ranges between 0.01 and 1.0 inclusive
(cond 
((> norm-level 0) ; normalize output to norm-level
(normalize (min 1.0 (max 0.01 (* norm-level 0.01))) 
(cond  ; 1
((= removal-choice 1) ; invert (delete) band of frequencies inside range
(sum (aref s 0) (mult reduction (aref s 1))
(highpass8 (aref s 1) upper)
(lowpass8 (aref s 1) lower)))
;
((= removal-choice 2) ; invert (delete) frequencies outside band range
(sum (aref s 0) (mult reduction (aref s 1))
(highpass8 (lowpass8 (aref s 1) upper) lower)))
;
(t ; invert one channel
(sum (aref s 0) (mult reduction (aref s 1)))))))
;
(t ; do not normalize output
(cond  ; 2
((= removal-choice 1) ; invert (delete) band of frequencies inside range
(sum (aref s 0) (mult reduction (aref s 1))
(highpass8 (aref s 1) upper)
(lowpass8 (aref s 1) lower)))
;
((= removal-choice 2) ; invert (delete) frequencies outside band range
(sum (aref s 0) (mult reduction (aref s 1))
(highpass8 (lowpass8 (aref s 1) upper) lower)))
;
(t ; invert one channel
(sum (aref s 0) (mult reduction (aref s 1)))))))))))

