;;; create the 'digital zipper' effect
;;; a not-very-debonair way to fade out file1 and fade in file2

(definstrument zipper (beg dur file1 file2 ramp-envelope frame-size &optional (ramp-envelope-base 1.0) frame-envelope)
  ;; pan between file1 and file2 using a "zipper-like" effect
  ;; ramp-env at 0 => all file1, at 1 => all file2, in between a mixture
  ;; frame-size is the basic speed at which the mixture takes place (dependent also on frame-env)
  ;; ramp-env-base is the base of the panning envelope (for exponential pans)
  ;; frame-env affects the frame size -- don't let it get to 0!!
  (let* ((f1 (open-input* file1))
	 (f2 (if f1 (open-input* file2) (close-input f1))))
    (if (not f1) (warn "can't find ~A" file1))
    (if (not f2) (warn "can't find ~A" file2))
    (when (and f1 f2)
      (unwind-protect
	  (if (and frame-envelope (<= (min-envelope frame-envelope) 0.0))
	      (progn
		(warn "zipper can't use frame-env ~A because it goes to ~F" frame-envelope (min-envelope frame-envelope))
		(setf frame-envelope nil)))
	(let* ((start (floor (* *srate* beg)))
	       (end (+ start (floor (* *srate* dur))))
	       (tframe-envelope (or frame-envelope '(0 1 1 1)))
	       (fe (make-env tframe-envelope :scaler (* *srate* frame-size) :duration dur))
	       (maxframe (max-envelope tframe-envelope))
	       (ctr1 0)
	       (ctr2 0)
	       (frame-loc 0)
	       (ramp-loc 0.0)
	       (rampe (make-env ramp-envelope :duration dur :base ramp-envelope-base))
	       (trigger 0)
	       (frame-samples (1+ (ceiling (* *srate* maxframe frame-size))))
	       (frame (make-double-array frame-samples))
	       (frame1 (make-double-array frame-samples))
	       (frame2 (make-double-array frame-samples))
	       (cursamples 0)
	       (chunk-len 0)
	       (low-start 20)
	       )
	  (run
	   (loop for i from start to end do
	     (declare (type :integer ctr1 ctr2 trigger frame-loc cursamples chunk-len low-start frame-samples)
		      (type :float ramp-loc low-start)
		      (type :double* frame frame1 frame2))
	     ;; read straight file input if outside ramp section
	     (setf ramp-loc (env rampe))
	     (setf frame-samples (floor (env fe)))
	     (setf chunk-len (floor (* frame-samples ramp-loc)))
	     ;; fe's duration assumes it's being called on every sample, but we only need this value every once in a while
	     (if (<= chunk-len low-start)
		 (progn
		   (outa i (ina ctr1 f1))
		   (incf ctr1))
	       (if (>= chunk-len (- frame-samples low-start))
		   (progn
		     (outa i (ina ctr2 f2))
		     (incf ctr2))
		 ;; else we're in the ramp phase
		 ;;  read frame if we're within its bounds
		 (if (= trigger 0)
		     (outa i (aref frame frame-loc))
		   ;; now get next portion of the ramp
		   (progn
		     (setf cursamples frame-samples)
		     (dotimes (k cursamples)
		       (setf (aref frame1 k) (ina ctr1 f1))
		       (incf ctr1)
		       (setf (aref frame2 k) (ina ctr2 f2))
		       (incf ctr2))
		     ;; now resample each dependent on location in ramp (samp1 and samp2 are increments)
		     (clear-array frame)
		     (let ((start-ctr 0.0)
			   (samp2 (/ frame-samples chunk-len)))
		       (dotimes (k chunk-len)
			 (let* ((ictr (floor start-ctr))
				(y0 (aref frame2 ictr))
				(y1 (aref frame2 (+ ictr 1))))
			   (setf (aref frame k) (+ y0 (* (- y1 y0) (- start-ctr ictr))))
			   (incf start-ctr samp2))))
		     (let ((start-ctr 0.0)
			   (m chunk-len)
			   (samp1 (/ frame-samples (- frame-samples chunk-len))))
		       (dotimes (k (- frame-samples chunk-len))
			 (let* ((ictr (floor start-ctr))
				(y0 (aref frame1 ictr))
				(y1 (aref frame1 (+ ictr 1))))
			   (setf (aref frame m) (+ y0 (* (- y1 y0) (- start-ctr ictr))))
			   (incf start-ctr samp1)
			   (incf m))))
		     (outa i (aref frame 0))))))
	     (incf frame-loc)
	     (setf trigger 0)
	     (if (>= frame-loc cursamples)
		 (progn
		   (setf frame-loc 0)
		   (setf trigger 1))))))
	)
      (progn
	(close-input f1)
	(close-input f2)))))

;;; (with-sound () (zipper 0 3 "mb.snd" "fyow.snd" '(0 0 1.0 0 1.5 1.0 3.0 1.0) .025))
;;; (with-sound () (zipper 0 3 "mb.snd" "fyow.snd" '(0 0 1.0 0 1.5 1.0 2.0 1.0 2.5 0.0 3.0 1.0) .015))
;;; (with-sound () (zipper 0 3 "mb.snd" "fyow.snd" '(0 0 1.0 0 1.5 .5 2.0 .1 2.5 0.7 3.0 0.0) .05))
;;; (with-sound () (zipper 0 4 "mb.snd" "fyow.snd" '(0 0 1.0 0 1.5 .5 2.0 .1 2.5 0.7 3.0 0.0 4.0 0.0) .03 1.0 '(0 1 1 2)))
;;; mb.snd is a take from Madame Butterfly, fyow.snd is M.C. saying "Yeah right, Bill"
;;; this is also good if the same file is used twice -- sort of like a CD player gone berserk
