;;; -*- syntax: common-lisp; base: 10; mode: lisp -*-
;;;
;;; several examples of the convolve unit generator

(definstrument cnv (beg dur filter file &optional (amp 1.0) (chan 0))
  (let* ((start (floor (* beg *srate*)))
	 (end (+ start (floor (* dur *srate*))))
	 (fil (open-input file :channel chan))
	 (filt (open-input filter))
	 (ff (make-convolve :input fil :filter filt)))
    (run
     (loop for i from start to end do (outa i (* amp (convolve ff)))))
    (close-input filt)
    (close-input fil)))

#|
(with-sound (:output "/zap/1.snd") (outa 0 .5) (outa 3000 .5) (outa 10000 .5))
(with-sound (:output "/zap/v.snd") (fm-violin 0 1 440 .1 :amp-env '(0 1 100 1)))
(with-sound (:statistics t) (cnv 0 1 "/zap/1.snd" "/zap/v.snd"))
(with-sound (:statistics t) (cnv 0 6 "fyow.snd" "fyow.snd" .1))
|#

(definstrument cnvo (beg dur filter file &optional (size 512))
  (let* ((start (floor (* beg *srate*)))
	 (end (+ start (floor (* dur *srate*))))
	 (fil (open-input file))
	 (filt (make-double-float-array (length filter) :initial-contents filter))
	 (ff (make-convolve :input fil :filter filt :fft-size size)))
    (run
     (loop for i from start to end do (outa i (convolve ff))))
    (close-input fil)))

#|
(with-sound (:statistics t) (cnvo 0 1 '(.5 .2 .1 .05 0 0 0 0) "/zap/v.snd" 64))
|#

;;; reverberation via convolution with exponentially decaying Gaussian white noise,
;;;   this kind of reverb was recommended many years ago by J.A.Moorer in
;;;   "About this Reverberation Business" reprinted in "Foundations of Computer Music".

(defvar last-decay-time 0.0)
(defvar last-decay-expt 0.0)
(defvar decay-file "/zap/decay.snd")

(definstrument cnv-reverb (beg dur &key (rev-amt .1) (decay-time 1.0) (decay-expt 32.0))
  ;; this performs the convolution as one huge fft operation
  (let* ((start (floor (* beg *srate*)))
	 (end (+ start (floor (* dur *srate*))))
	 (reverb-file *input*)
	 (len (floor (* *srate* decay-time)))
	 (start-samp (floor (* .022 *srate*))))
    (when (or (/= last-decay-time decay-time)
	      (/= last-decay-expt decay-expt))
      (princ "creating impulse response...") (force-output)
      (let* ((fe (make-env :envelope '(0 1 100 0) :start start-samp :end len :scaler .1 :base decay-expt))
	     (ofile (open-output decay-file)))
	(setf last-decay-time decay-time)
	(setf last-decay-expt decay-expt)
	(outa (floor (* .01 *srate*)) .2 ofile)
	(outa (floor (* .014 *srate*)) .15 ofile)
	(outa (floor (* .02 *srate*)) .12 ofile)
	(loop for i from start-samp below len do
	  (let ((sum 0.0))
	    (dotimes (i 10) (incf sum (- (random .4) .2))) 
	    ;;Gaussian noise -- 0 centered much better than pure positive case
	    (outa i (* (env fe) sum) ofile)))
	(close-output ofile))
	(print "done!") (force-output))
    (let* ((ifile (open-input decay-file))
	   (ff (make-convolve :input reverb-file :filter ifile)))
      (run
       (loop for i from start to end do (outa i (* rev-amt (convolve ff)))))
      (close-input ifile))))

#|
(with-sound (:reverb cnv-reverb :statistics t) (fm-violin 0 .1 440 .1 :reverb-amount .1) (fm-violin 2 .1 660 .1 :reverb-amount .05))
|#

;;; similar but use an array to hold the impulse response (and therefore use overlap-add internally --
;;;   this is less sensitive to memory limitations than the cnv-reverb above).

(definstrument cnvrev (file impulse &optional (rev-amt .1))
  (let* ((file-len (sound-frames file))
	 (filter-len (sound-frames impulse))
	 (filter-chan0 (make-double-float-array filter-len))
	 (filter-chan1 (if (and (= (mus-channels *output*) 2) (> (sound-chans impulse) 1))
			   (make-double-float-array filter-len))))
    (file->array impulse 0 0 filter-len filter-chan0)
    (if filter-chan1
	(file->array impulse 1 0 filter-len filter-chan1)
      (setf filter-chan1 filter-chan0))
    (let* ((fd (open-input file))
	   (fd1 (if (and (= (mus-channels *output*) 2) (> (sound-chans file) 1)) (open-input file :channel 1)))
	   (ff0 (make-convolve :input fd :filter filter-chan0))
	   (ff1 (if (and (= (mus-channels *output*) 2) (> (sound-chans file) 1))
		    (make-convolve :input fd1 :filter filter-chan1))))
      (run
       (loop for i from 0 to (+ file-len filter-len) do
	 (outa i (* rev-amt (convolve ff0)))
	 (if (and (= (mus-channels *output*) 2) ff1) (outb i (* rev-amt (convolve ff1))))))
      (close-input fd)
      (if fd1 (close-input fd1)))))

;;; (with-sound (:statistics t :scaled-to .5 :srate 44100 :channels 2) (cnvrev "fy.snd" "fyow.snd"))
