;;;
;;;
;;; rec.ins:  Simulation of Baroque Soprano recorder
;;;
;;; Bob Lantz, Music 220B 
;;; 
;;; final project/assignment 6
;;;
;;; "rec" instrument features:
;;;
;;;	2 FM pairs, generating odd and even harmonics
;;;	Periodic & Random Vibrato
;;; 	Pitch Skew
;;;	Slightly varying attack times
;;;	Filtered noise
;;;
;;; Borrows from: bsn2ex.ins (mainly)

(defInstrument rec (start-time duration frequency amplitude

		          &key (odd-C-ratio 1.0)
			       (odd-M-ratio 2.0)
			       (odd-index0 0.0)
			       (odd-index1 .9)
		               (odd-index-env '(0 0  20 1 40 1  60 1 80 1 100 .5))
			       
			       (even-C-ratio 2.0)
			       (even-M-ratio 2.0)
			       (even-index0 0.0)
			       (even-index1 1.0)
		               (even-index-env '(0 0 20 1 40 .9  60 .9 80 .1 100 .5))
			     
		               (amp-env '(0 0  20 .7 40 1 60 1 80 .10 100 0))
		               (degree 45.0)
		               (distance 1.0)
		               (reverb-amount 0.005)
			       
			       ; default attack and decay times
			        (at1-time .06)
		       		(at2-time .11)
		       		(dc1-time 0.011)
		       		(dc2-time .1)
				
				; pitch skew function
				(skew-fun 
				'(0 -1.0 20 -.4 40 0 60 0 80 .1 100 .2))
 
  				; vibrato, stolen from bsn2ex
	 			 (vib-freq 4)
				 (vib-depth .01) 
				 (ran-frq 5) 
				 (ran-depth .005)

			)
			       
			       
  (let* ((beg (floor (* start-time sampling-rate)))
	 (end (+ beg (floor (* duration sampling-rate))))


	; attack time gets smaller as pitch goes higher.
	; 
	; pitch		at1-time	at2-time
	; c5		.08		.09
	; c6		.011		.01
	; c7		.01		.4
	
	
	; for now, I will just interpolate, more or less, among these times
	; as follows:
	; if pitch <= c5, at1-time = .08 and at2-time = .09
	; if c6 <= pitch < c6, interp-val = (pitch - c6)/(c5 - c6)
	; 	and at1-time = .08 - some-factor * interp-val
	;	and at2-time likewise
	
	(interp-val (cond ((< frequency (pitch 'c5)) 0)
			  ((< frequency (pitch 'c6)) (/ (- frequency (pitch 'c6))
			  				(- (pitch 'c6) (pitch 'c5))))
			  (t 1)))
			  
	(at1-time (- at1-time (* .03 interp-val)))
	(at2-time (- at2-time (* .02 interp-val)))
	
	; note that c7 actually has an amplitude envelope quite different
	; from c5 or c6, so the above (e.g. .4) is just an approximation
	
	
	; additionally, I would like to scale the even harmonics' amplitude so that 
	; by the time c7 is reached, the even component is much smaller
	; even-scaler = (1 - .75 * interp-val)
	
	(even-scaler (- 1.0 (* .75 interp-val)))
	
	; so, the even-amp-scaler will be 1 at c5, and .01 at c7
	; Odd harmonics fm pair
	; note that oscillators have a frequency of zero because
	; the frequency is determined by the modulating input,
	; which combines vibrato, pitch skew, and fm
	
	 (odd-C-frq (* odd-C-ratio frequency))
	 
	 (odd-carrier (make-oscil :frequency 0.0))
					    
	 (odd-M-frq (* odd-M-ratio frequency))
	 
	 (odd-modulator (make-oscil :frequency 0.0))
					      
	 (odd-fm-env (make-env :envelope odd-index-env
			   :offset (* odd-index0 odd-M-ratio frequency)
			   :scaler (* (- odd-index1 odd-index0) 
			   			odd-M-ratio frequency)
			   :start-time start-time 
			   :duration duration))
			   
	; even-harmonics fm pair
   
   	 (even-C-frq (* even-C-ratio frequency))
   
	 (even-carrier (make-oscil :frequency 0))
	 					    
	 (even-M-frq (* even-M-ratio frequency))
	 
	 (even-modulator (make-oscil :frequency 0.0))	
	 				      
	 (even-fm-env (make-env :envelope even-index-env
			   :offset (* even-index0 even-M-ratio frequency)
			   :scaler (* (- even-index1 even-index0) 
			   			even-M-ratio frequency)
			   :start-time start-time 
			   :duration duration))

	; location and amplitude envelope
	
	 (loc (make-locsig :degree degree 
			   :distance distance 
			   :revscale reverb-amount))
			   
	; note we want to use the *original* amplitude envelope for
	; both amp-env and vib-fun; so, we duplicate it as "amp1-env"
	
	 (amp-env1 amp-env)
	 
	; amplitude env
	 (amp-env (make-env :envelope 
	 		(divenv amp-env1 duration
				20 at1-time
				40 at2-time
				60 (- duration at1-time at2-time dc1-time dc2-time)
				80 dc1-time
				100 dc2-time
			)
			:scaler amplitude 
			:start-time start-time 
			:duration duration))

	; pitch skew, stolen from bsn2ex

	  (skew-env (make-env :envelope
	      	 	(divenv skew-fun duration 
	      			20 at1-time 
				40 at1-time
		      		60 (- duration at1-time at1-time dc2-time dc2-time)
				80 dc2-time 100 dc2-time
			)
			:scaler (* .02 amplitude)
			:start-time start-time 
			:duration duration))
				  
	; periodic & random vibrato, stolen from bsn2ex

	 (ran-dev (make-randi :frequency ran-frq :amplitude ran-depth))
	 (vib-frq (make-oscil :frequency vib-freq))

	 (vib-fun (make-env :envelope
		    	(divenv amp-env1 duration 
			    20 at1-time 40 at2-time
			    60 (- duration at1-time at2-time dc1-time dc2-time)
			    80 dc1-time 100 dc2-time
			 )
			 :scaler vib-depth 
			 :start-time start-time 
			 :duration duration))

	; noise, for simulation of air/breath
	
	  (noise (make-randi 
			:frequency sampling-rate 
			:amplitude .003 ))
			
	; sum-frq is our scaler for input frequency (i.e. for pitch skew
	; as well as vibrato, and sum-val is the raw value...		
	 (sum-frq 0.0)
	 (sum-val 0.0)	
	 
	; we want to filter the noise a bit so that it sounds like air
	; going through the instrument
	
	 (bandwidth .99)
	 (gain 1.0)
	 (noise-filter (make-formnt bandwidth frequency gain))
   
	) ; let*
			    
    (Run
     (loop for i from beg to end do
     
     	; sum-val is the sum of all pitch skews, and is also
	; used for tremolo
     	(setf sum-val (+ 1.0 
       			 (env skew-env)
			 (randi ran-dev)
			 (* (env vib-fun) 
			    (oscil vib-frq))
				))

       ; set sum-frq to be sum of current pitch skew and random + periodic vibrato
       (setf sum-frq (In-hz sum-val))
            
       (locsig loc i (* (env amp-env)	
			; scale everything by sum-val for tremolo
       			sum-val
       			; odd is scaled by .9, even by .09, and noise is very small...
			(+
       				(* .9 (oscil odd-carrier 
					(* sum-frq
					   ; base frequency = sum-frq * odd-C-frq
					   (+ odd-C-frq
					   ; we also scale the modulating frequency
					   (* (env odd-fm-env)
					      (oscil odd-modulator
							(* sum-frq odd-M-frq)))))))
		
				(* even-scaler 
				   .09 (oscil even-carrier 
					   (* sum-frq
					      (+ even-C-frq (* (env even-fm-env)
					  	 (oscil even-modulator
						 	(* sum-frq even-M-frq)))))))
							
				; add in filtered noise
				(* 100 (formnt noise-filter (randi noise)))
			)		  
			))))))

; Part definition

(defpart rec (clm-part)
	 (name time duration frequency amplitude 
	 	&message odd-C-ratio 
	       odd-M-Ratio even-C-ratio even-M-ratio
	       even-index0 even-index1 even-index-env amp-env degree distance
	       odd-index0 odd-index1 odd-index-env
	       reverb-amount at1-time at2-time dc1-time dc2-time
	       vib-frq vib-depth vib-fun ran-dev ran-frq ran-depth
	       skew-env skew-fun skew-amt sum-frq)

 (duration frequency amplitude odd-C-ratio odd-M-Ratio
	   even-C-ratio even-M-ratio
	       even-index0 even-index1 even-index-env
	       odd-index0 odd-index1 odd-index-env amp-env degree distance
	       
	       odd-C-frq odd-M-frq
	       even-C-frq even-M-frq
	       
	       reverb-amount
	       beg end odd-carrier odd-modulator loc fm-env
 		at1-time at2-time dc1-time dc2-time
		vib-frq vib-depth ran-frq ran-depth vib-fun ran-dev
		skew-env skew-fun skew-amt sum-frq)
)
