;;; -*- syntax: common-lisp; base: 10; mode: lisp -*-
;;;
;;; Perry Cook's maraca from CMJ vol 21 no 3 (Fall 97) p 44

(definstrument maraca (beg dur &optional (amp .1) 
			   (sound-decay 0.95) 
			   (system-decay 0.999) 
			   (probability .0625)
			   (shell-freq 3200.0)
			   (shell-reso 0.96))
  (let* ((st (floor (* *srate* beg)))
	 (nd (+ st (floor (* *srate* dur))))
	 (temp 0.0)
	 (shake-energy 0.0)
	 (snd-level 0.0)
	 (input 0.0)
	 (output (make-double-array 2))
	 (coeffs (make-double-array 2))
	 (num-beans 64)
	 (j 0)
	 (sndamp (/ amp 16384.0))
	 (srate4 (floor *srate* 4))
	 (gain (/ (* (log num-beans 4) 40) num-beans)))
    ;; gourd resonance filter
    (setf (aref coeffs 0) (double (* -2.0 shell-reso (cos (hz->radians shell-freq)))))
    (setf (aref coeffs 1) (double (* shell-reso shell-reso)))
    (run
     (loop for i from st to nd do
       (when (< temp two-pi)
	 ;; shake over 50msec and add shake energy
	 (incf temp (hz->radians 20))
	 (incf shake-energy (- 1.0 (cos temp))))
       (when (= j srate4)		;shake 4 times/sec
	 (setf temp 0.0)
	 (setf j 0))
       (incf j)
       (setf shake-energy (* shake-energy system-decay))
       ;; if collision, add energy
       (if (< (random 1.0) probability) (incf snd-level (* gain shake-energy)))
       ;; actual sound is random
       (setf input (* snd-level (centered-random 1.0)))
       ;; compute exponential sound decay
       (setf snd-level (* snd-level sound-decay))
       ;; gourd resonance filter calc
       (setf input (- input 
		      (* (aref output 0) (aref coeffs 0)) 
		      (* (aref output 1) (aref coeffs 1))))
       (setf (aref output 1) (double (aref output 0)))
       (setf (aref output 0) (double input))
       ;; extra zero for spectral shape, also fixup amp since Perry is assuming maxamp 16384
       (outa i (* sndamp (- (aref output 0) (aref output 1))))))))

;;; maraca: (with-sound () (maraca 0 5 .5))
;;; cabasa: (with-sound () (maraca 0 5 .5 0.95 0.997 0.5 3000.0 0.7))

(definstrument big-maraca (beg dur &optional (amp .1) 
			       (sound-decay 0.95) 
			       (system-decay 0.999) 
			       (probability .0625)
			       (shell-freqs '(3200.0))
			       (shell-resos '(0.96))
			       (randiff .01)
			       (with-filters t))
  ;; like maraca, but takes a list of resonances and includes low-pass filter (or no filter)			   
  (let* ((st (floor (* *srate* beg)))
	 (nd (+ st (floor (* *srate* dur))))
	 (temp 0.0)
	 (temp1 0.0)
	 (resn (length shell-freqs))
	 (shake-energy 0.0)
	 (snd-level 0.0)
	 (input 0.0)
	 (sum 0.0)
	 (last-sum 0.0)
	 (last-diff 0.0)
	 (diff 0.0)
	 (output (make-double-array (list resn 2)))
	 (coeffs (make-double-array (list resn 2)))
	 (basesf (make-double-array resn))
	 (num-beans 64)
	 (j 0)
	 (sndamp (/ amp (* 16384.0 resn)))
	 (srate4 (floor *srate* 4))
	 (gain (/ (* (log num-beans 4) 40) num-beans)))
    ;; gourd resonance filters
    (loop for i from 0 below resn and shell-freq in shell-freqs and shell-reso in shell-resos do
      (setf (aref coeffs i 0) (double (* -2.0 shell-reso (cos (hz->radians shell-freq)))))
      (setf (aref basesf i) (double (aref coeffs i 0)))
      (setf (aref coeffs i 1) (double (* shell-reso shell-reso))))
    (run
     (loop for k from st to nd do
       (when (< temp two-pi)
	 ;; shake over 50msec and add shake energy
	 (incf temp (hz->radians 20))
	 (incf shake-energy (- 1.0 (cos temp))))
       (when (= j srate4)		;shake 4 times/sec
	 (setf temp 0.0)
	 (setf j 0))
       (incf j)
       (setf shake-energy (* shake-energy system-decay))
       ;; if collision, add energy
       (when (< (random 1.0) probability) 
	 (incf snd-level (* gain shake-energy))
	 ;; randomize res freqs a bit
	 (loop for i from 0 below resn do
	   (setf (aref coeffs i 0) (double (+ (aref basesf i) (centered-random randiff))))))
       ;; actual sound is random
       (setf input (* snd-level (centered-random 1.0)))
       ;; compute exponential sound decay
       (setf snd-level (* snd-level sound-decay))
       ;; gourd resonance filter calcs
       (setf temp1 input)
       (setf last-sum sum)
       (setf sum 0.0)
       (loop for i from 0 below resn do
	 (setf input temp1)
	 (setf input (- input 
			(* (aref output i 0) (aref coeffs i 0)) 
			(* (aref output i 1) (aref coeffs i 1))))
	 (setf (aref output i 1) (double (aref output i 0)))
	 (setf (aref output i 0) (double input))
	 (incf sum input))
       (if with-filters
	   (progn
	     (setf last-diff diff)
	     (setf diff (- sum last-sum))
	     (setf temp1 (+ last-diff diff)))
	 (setf temp1 sum))
       ;; extra zero for spectral shape, also fixup amp since Perry is assuming maxamp 16384
       (outa k (* sndamp temp1))))))

;;; tambourine: (with-sound () (big-maraca 0 1 .25 0.95 0.9985 .03125 '(2300 5600 8100) '(0.96 0.995 0.995) .01))
;;; sleighbells: (with-sound () (big-maraca 0 2 .5 0.97 0.9994 0.03125 '(2500 5300 6500 8300 9800) '(0.999 0.999 0.999 0.999 0.999)))
;;; sekere: (with-sound () (big-maraca 0 2 .5 0.96 0.999 .0625 '(5500) '(0.6)))
;;; windchimes: (with-sound () (big-maraca 0 2 .5 0.99995 0.95 .001 '(2200 2800 3400) '(0.995 0.995 0.995) .01 nil))
