[CM] Chaining effects together

Jeremy Shaw jeremy.shaw@lindows.com
Tue, 16 Jul 2002 12:01:05 -0700


Hello,

Does anyone have a good scheme for chaining filters (and effects)
together?

Here is some pseudo-code that won't work, but shows what I am
trying to figure out how to do:

(with-sound () (delay (:delay-time 1.0 :regen 0.9 :mix .5)
		      (low-pass-filter (:fc 500 :lfo-amt 200 :lfo-freq 0.3)
				       (saw 0 1 200 0.5)
				       (saw 1 1 400 0.5)
				       (saw 2 1 800 0.5)
				       (saw 3 1 400 0.5)
				       (saw 4 1 200 0.5))))

I started implementing a scheme, where "saw" writes its output to
*reverb*, and then delay reads from *reverb* and writes to *output*.

My current implementation has several problems:

(1) saw+delay works, saw+filter works, but saw+filter+delay fails
because filter is writing to *output* while delay is reading from
*reverb*.

(2) If I want just a plain saw wave, I have to use a filter that does
nothing but copy *reverb* to *output*.

(3) Makes real-time (aka with-dac) impractical.

I think I can address (1) and (2), but I am wondering if there is a
better way...

Thanks!
Jeremy Shaw.


Here the above mentioned scheme.

;;; -*- syntax: common-lisp; base: 10; mode: lisp -*-

(in-package :clm)

;; WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
;; WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
;;
;; This code exhibits a scheme for chaining effects together that DOES NOT WORK.
;;
;; WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
;; WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 

#|

;; Play the saw wave, then play it with delay added (this works)

(with-sound ()
	    (mix (with-sound (:reverb unity :revfile "plain.rev" :output "plain")
			     (good-saw 0 1 200 0.3)))
	    (mix (with-sound (:reverb e-delay :reverb-data (:delay-time 0.5) :output "delay")
			     (good-saw 1.5 0.2 200 0.3))))

;; This does not work, it tries to play a saw with filtering and delay.
;; The delay expects to read its input from *reverb* but the filter is writing its output to *output*
;;
(with-sound (:reverb e-delay :reverb-data (:delay-time 0.5) :output "delay")
	    (with-sound (:reverb ch-low-pass :reverb-data (:fc 400 :qc 0.3) :revfile "filter.rev" :output "filter")
			(good-saw 1 1 200 0.05)))
|#


;; Generate a band limited sawtooth-wave
;;
;; Based on ideas presented in "Alias-Free Digital Synthesis of Classic Analog Waveforms"
;;  by Tim Stiltin
;; http://www-ccrma.stanford.edu/~stilti/papers/Welcome.html
;;
;; NOTES: This still needs work. At the very least, I think I need to adjust the DC-offset

(definstrument good-saw (start-time duration frequency amplitude 
				&optional (a0 1.0) (b1 -0.98) (amp-env '(0 1 99 1 100 0)))
  (multiple-value-bind (beg end) (times->samples start-time duration)
    (let ((s (make-sum-of-cosines :cosines (floor (/ *srate* (* 2 frequency)))
				  :frequency frequency))
	  (f-lowpass (make-one-pole :a0 a0
				    :b1 b1))
	  (amp (make-env :envelope amp-env
			 :scaler amplitude
			 :duration duration)))
      (run 
       (loop for i from beg below end do
	     (outa i (* (env amp) (one-pole f-lowpass (sum-of-cosines s))) *reverb*))))))

;; A 2 pole low-pass filter
;; Sometimes called the Chamberlain filter a State Variable Filter ??
;;
;; "Filters, Delays, Modulations and Demodulations: A tutorial"
;;  by Dutilleux, Pierre
;; http://www.iua.upf.es/dafx98/papers/
;;
;; Also see: http://www.cen.uiuc.edu/~ece320/handouts/chamberlin.ps
;;
;; NOTE: This filter does not noramilze the output and is generally way to loud
(definstrument ch-low-pass (start-time duration &key (fc 500) (qc 1.0))
  (multiple-value-bind (beg end) (times->samples start-time duration)
    (let* ((f (* 2 (sin (/ (* 3.1415926535 fc) *srate*))))
	   (b1 (* -1 (- 2 (* f qc) (* f f))))
	   (b2 (- 1 (* f qc)))
	   (a0 (+ 1 b1 b2))
	   (f1 (make-two-pole a0 b1 b2))
	   (f2 (make-two-pole a0 b1 b2))
	   (f3 (make-two-pole a0 b1 b2))
	   (f4 (make-two-pole a0 b1 b2)))
      (run
       (progn
	 (loop for i from beg below end do
	       (outa i (two-pole f1 (two-pole f2 (two-pole f3 (two-pole f4 (ina i *reverb*))))))))))))

(definstrument e-delay (start-time duration &key (delay-time 0.5) (decay-time 10) (regen 0.8))
  (multiple-value-bind (beg end) (times->samples start-time duration)
    (let ((d (make-delay (* delay-time 44100)))
	  (b (make-env :envelope '(0 1 75 1 100 0)
		       :scaler 1.0
		       :duration decay-time)))
      (run
       (progn
	 (loop for i from beg below end do
	       (outa i (+ (ina i *reverb*) (delay d (* regen (+ (tap d) (ina i *reverb*)))))))
	 (loop for i from end to (+ end (* decay-time *srate*)) do
	       (outa i (* (env b) (delay d (* regen (tap d)))))))))))


;; Read from *reverb* stream and send it to the *output* stream unmodified.
;;
;; Unity is probably not the best name, but I can't remember the name I really want.
(definstrument unity (start-time duration)
  (multiple-value-bind (beg end) (times->samples start-time duration)
    (run
     (loop for i from beg below end do
	   (outa i (ina i *reverb*))))))