Music 220B

Generating Vowels by Subtractive Synthesis

Source/Filter Vocal Model

One way to generate vocal sounds with a computer is to model the acoustic mechanism used in phonation. We can consider this as a system of a periodic source (produced by the glottal folds) and a group of resonating cavities (the naso-pharynx chambers) that filter the spectrum of the source. A simple computer model of this can consist of a pulse train generator (modeling the vocal folds), and a set of formant filters (modeling the resonant cavities). This approach can be called Subtractive Synthesis, in contrast to Additive Synthesis where we add sines together to construct a sound, here we take a sound source with a rich spectrum (such as a train of pulses) and we filter it to create our vocal-like sounds.

A CLM vowel instrument

To create a Source/Filter model able to produce vocal-like tones we should know what the parameters of the vocal system are, specially where the formats of the vowels we want to produce are placed in the spectrum, which are their amplitudes and how wide they are in frequency. This file contains data with these parameters for vowels /A/, /E/, /I/, /O/ and /U/ for male and female voices. Five formants are given here but we will use only the first three in our instrument.

If we want our instrument to change vowels on the fly, we should provide it with a kind of parametric data base. There are tons of ways to do this, maybe the easiest one is to create the data base locally inside the Run-Loop, doing so we will be able to restore whatever vowel we want on run time. Other thing we may want our instrument being capable of is changing the frequency of the source and (why not) changing the source itself from pulses to noise. Here is our instrument:

;;; vowels.cl
;;; real-time subtractive synthesis
;;; producing vocal like sounds
;;; formant data is stored as a set of presets

;;; instrument on/off 
(defparameter vowels-recon (control-allocate))
;;; input level control
(defparameter vowels-in-level (control-allocate))
;;; filter frequencies
(defparameter vowels-1-frq (control-allocate))
(defparameter vowels-2-frq (control-allocate))
(defparameter vowels-3-frq (control-allocate))
;;; filter radius
(defparameter vowels-1-bw (control-allocate))
(defparameter vowels-2-bw (control-allocate))
(defparameter vowels-3-bw (control-allocate))
;;; filter gains
(defparameter vowels-1-g (control-allocate))
(defparameter vowels-2-g (control-allocate))
(defparameter vowels-3-g (control-allocate))
;;; input monitor
(defparameter vowels-in-frq (control-allocate))
(defparameter vowels-in-sel (control-allocate))
(defparameter vowels-line-in (control-allocate))
;;; output level
(defparameter vowels-out (control-allocate))
;;; presets
(defparameter vowels-set-1 (control-allocate))
(defparameter vowels-set-2 (control-allocate))
(defparameter vowels-set-3 (control-allocate))
(defparameter vowels-set-4 (control-allocate))
(defparameter vowels-set-5 (control-allocate))

;;; macro needed to properly compute filter's radius
;;; from a given bandwidth in Hertz
(defmacro compute-radius (bw)
  `(- 1 (/ (* pi ,bw) ,sampling-rate)))

;;; instrument
(defpinstrument vowels (&key (tester nil))
  (let* ((noi (make-randh :frequency 5000 :amplitude .1)) ;;; noise generator
	 (pulse (make-sum-of-cosines :cosines 30           ;;; pulse train generator
				     :frequency 5000))
	 ;;; filters
	 (filter-1 (make-formant 0.99 175.0))
	 (filter-2 (make-formant 0.99 610.0))
	 (filter-3 (make-formnt 0.99 1600.0))
	 ;;; input level contol
	 (levelf (make-fcontrol vowels-in-level))
	 ;;; noise/pulses frequency
	 (frq-inf (make-fcontrol vowels-in-frq))
	 ;;; output level control
	 (outf (make-fcontrol vowels-out))
	 ;;; frequency controls
	 (frqf-1 (make-fcontrol vowels-1-frq))
	 (frqf-2 (make-fcontrol vowels-2-frq))
	 (frqf-3 (make-fcontrol vowels-3-frq))
	 ;;; radius controls
	 (rf-1 (make-fcontrol vowels-1-bw))
	 (rf-2 (make-fcontrol vowels-2-bw))
	 (rf-3 (make-fcontrol vowels-3-bw))
	 ;;; gain controls
	 (gf-1 (make-fcontrol vowels-1-g))
	 (gf-2 (make-fcontrol vowels-2-g))
	 (gf-3 (make-fcontrol vowels-3-g)))
  (run
   (loop for i from 0 do
	 (when (= (control vowels-recon) 0.0) (loop-finish))
	 ;;; some male bass formants as presets
	 ;;; A
	 (cond ((= (control vowels-set-1) 1.0)
		;;; frq
		(setf (control vowels-1-frq) 600.0)
		(setf (control vowels-2-frq) 1040.0)
		(setf (control vowels-3-frq) 2250.0)
		;;; bw
		(setf (control vowels-1-bw) 60.0)
		(setf (control vowels-2-bw) 70.0)
		(setf (control vowels-3-bw) 110.0)
		;;; g
		(setf (control vowels-1-g) 1.0)
		(setf (control vowels-2-g) 0.5)
		(setf (control vowels-3-g) 0.35)
		;;; release button
		(setf (control vowels-set-1) 0.0)
		)
	       ;;; E
	       ((= (control vowels-set-2) 1.0)
		;;; frq
		(setf (control vowels-1-frq) 400.0)
		(setf (control vowels-2-frq) 1620.0)
		(setf (control vowels-3-frq) 2400.0)
		;;; bw
		(setf (control vowels-1-bw) 40.0)
		(setf (control vowels-2-bw) 80.0)
		(setf (control vowels-3-bw) 100.0)
		;;; g
		(setf (control vowels-1-g) 1.0)
		(setf (control vowels-2-g) 0.25)
		(setf (control vowels-3-g) 0.35)
		;;; release button
		(setf (control vowels-set-2) 0.0))
	       ;;; I
	       ((= (control vowels-set-3) 1.0)
		;;; frq
		(setf (control vowels-1-frq) 250.0)
		(setf (control vowels-2-frq) 1750.0)
		(setf (control vowels-3-frq) 2600.0)
		;;; bw
		(setf (control vowels-1-bw) 60)
		(setf (control vowels-2-bw) 90.0)
		(setf (control vowels-3-bw) 100.0)
		;;; g
		(setf (control vowels-1-g) 1.0)
		(setf (control vowels-2-g) 0.1)
		(setf (control vowels-3-g) 0.2)
		;;; release button
		(setf (control vowels-set-3) 0.0))
	       ;;; O
	       ((= (control vowels-set-4) 1.0)
		;;; frq
		(setf (control vowels-1-frq) 400.0)
		(setf (control vowels-2-frq) 750.0)
		(setf (control vowels-3-frq) 2400.0)
		;;; bw
		(setf (control vowels-1-bw) 40.0)
		(setf (control vowels-2-bw) 80.0)
		(setf (control vowels-3-bw) 100.0)
		;;; g
		(setf (control vowels-1-g) 1.0)
		(setf (control vowels-2-g) 0.3)
		(setf (control vowels-3-g) 0.1)
		;;; release button
		(setf (control vowels-set-4) 0.0))
	       ;;; U
	       ((= (control vowels-set-5) 1.0)
		;;; frq
		(setf (control vowels-1-frq) 350.0)
		(setf (control vowels-2-frq) 600.0)
		(setf (control vowels-3-frq) 2400.0)
		;;; bw
		(setf (control vowels-1-bw) 40.0)
		(setf (control vowels-2-bw) 80.0)
		(setf (control vowels-3-bw) 100.0)
		;;; g
		(setf (control vowels-1-g) 1.0)
		(setf (control vowels-2-g) 0.1)
		(setf (control vowels-3-g) 0.03)
		;;; release button
		(setf (control vowels-set-5) 0.0))
	       (t nil))
	 ;;; set noise/pulses frq input
	 (if (= (control vowels-in-sel) 1.0)
	     (setf (frequency noi)(fcontrol frq-inf))
	   (setf (frequency pulse)(fcontrol frq-inf)))
	 ;;; set frequencies
	 (setf (frequency filter-1)(fcontrol frqf-1))
	 (setf (frequency filter-2)(fcontrol frqf-2))
	 (setf (frequency filter-3)(fcontrol frqf-3))
	 ;;; set radius
 	 (setf (formant-radius filter-1)(compute-radius (fcontrol rf-1)))
	 (setf (formant-radius filter-2)(compute-radius (fcontrol rf-2)))
	 (setf (formant-radius filter-3)(compute-radius (fcontrol rf-3)))
	 ;;; set gains
	 (setf (frmnt-g filter-1)(/ (fcontrol gf-1) 10.0))
	 (setf (frmnt-g filter-2)(/ (fcontrol gf-2) 10.0))
	 (setf (frmnt-g filter-3)(/ (fcontrol gf-3) 10.0))
	 ;;; select the input we want
	 (let* ((in-val (* (fcontrol levelf) 
			   (if (= (control vowels-line-in) 1.0)
			       (rec-any 0)
			     (if (= (control vowels-in-sel) 1.0)
				 (randh noi)
			       (sum-of-cosines pulse)))))
		;;; add output of the filters together
		(out-val (+ (formant filter-1 in-val) 
			    (formant filter-2 in-val)
			    (formant filter-3 in-val))))
	   ;;; send scaled value to the output
	   (outa i (* (fcontrol outf) out-val)) 
	   ;;; if tester is on send output to it
	   (if tester (setf (tester-in) out-val)))))))

Playing your instrument

After firing up Lisp in your Emacs editor, first load the tester (:ld tester). Second, you have to Save, Compile and Load the vowels.cl file (:cl vowels). Third (and last) you have to allocate memory for your graphic controls:
(open-controls 2048)
Now you are ready to go, start your controllers in a Xterm:
tester_lnx & ; vowels_lnx &
Once you place the controllers in your window you can play the instrument pasting this line to the Lisp interpreter:
(with-psound (:srate 22050)(tester) (vowels :tester t))


©1998 by Juan Pampin,
juan@ccrma.stanford.edu