;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;

;; ONE THE FLY AUDIO SIGNAL PROCESSING USING THE ICR

;; Look at example #56 for more on the ICR

;; 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Let's start by defining a "Code" instrument code1

;; And a "Code" effect code2

;; 

;; We'll chain the instrument throught the effect

;; 

(define code1 (au:make-node "aumu" "code" "MOSO" 2))

(define code2 (au:make-node "aumf" "code" "MOSO" 2))

(au:connect-node code1 0 code2 0)

(au:connect-node code2 0 *au:output-node* 0)

(au:update-graph)


;; we'll start by making a simple random noise effect


;; first we must create a kernel function with the type signature

;; [double,double,double,double,double*]*


(definec fxkernel1

   (lambda (sample-in:double time:double channel:double data:double*)

      (* .1 (random))))


;; then we assign the name of that kernel function to the audiounit

(au:code:set! code2 "fxkernel1")


;; add a sine wave out of the right channel

(definec fxkernel1

   (lambda (sample-in:double time:double channel:double data:double*)

      (let ((freq 220.0))

         (if (= channel 0.0)

             (* .1 (random))

             (* .1 (sin (* 3.141592 2.0 time freq *samplerate*)))))))


;; abstract out an oscillator function

(definec make-oscil

   (lambda (phase)      

      (lambda (amp freq)

         (let ((inc (* 3.141592 (* 2.0 (/ freq *samplerate*)))))

            (set! phase (+ phase inc))

            (* amp (sin phase))))))


;; now use make-oscil for left and right kernel oscillators

;; note that we capture losc and rosc as state for the fxkernel1

;; this is important because it means the losc and rosc are

;; maintained *between* individual calls to fxkernel1

(definec fxkernel1

   (let ((losc (make-oscil 0.0))

         (rosc (make-oscil 0.0)))

      (lambda (sample-in:double time:double channel:double data:double*)

         (if (= channel 0.0)

             (losc 0.5 440.0)

             (rosc 0.5 220.0)))))


;; finally let's set fxkernel1 back to passthrough

;; i.e. send the incoming sample straight back out

;; this will give us silence

;;

;; simple passthrough

(definec fxkernel1

   (lambda (sample-in:double time:double channel:double data:double*)

      sample-in))




;; now we'll make a simple sine instrument kernel

;; which must have a function type of 

;; [[double,double,double,double,double*]*]*

;; 

;; note the difference bewteen an instrment and an effect

;; most importantly an instrument kernel must 

;; RETURN A NEW CLOSURE

;; This is important to remember - each time a note is played

;; an instrument closure is asked to instantiate a new closure

;; An instrument kernel must take no arguments and return

;; a closure that takes a frequency, time, channel and data.

;; it is the returned closure that is actually used for 

;; making noise.

;; 

;; Note that you this structure lets you store state at the

;; instrument level and at the note level.

;; 

;; anyway onto a simple sinewave player example:

;; this looks very similar to the sine example from our

;; fxkernel and again uses make-oscil

(definec instrument-kernel1

   (lambda ()

      (let* ((losc (make-oscil 0.0))

             (rosc (make-oscil 0.0)))

         (lambda (freq:double time:double channel:double data:double*)

            (if (= channel 0.0)

                (losc 0.8 freq)

                (losc 0.8 (* 0.5 freq)))))))

            


;; now we assign the instrument-kernel1 to the AU

(au:code:set! code1 "instrument-kernel1")


;; now we need to make some notes !!

(define loop

   (lambda (beat pitch) 

      ;; note that the code AU supports sound

      ;; so we can use fractional pitches

      (sound code1 pitch 100 1/32)

      (sound code1 (- 108 pitch) 100 1/32)

      (callback (*metro* (+ beat (* 1/2 1/4))) 'loop (+ beat 1/4)

                (if (> pitch 72) 48 (+ pitch 1/2)))))


(loop (*metro* 'get-beat 4) 48)



;; you will probably want to switch to the utility process

;; before going futher.  

;; WHY?

;; At the moment the compiler itself is quite slow - meaning

;; that it compiles your source code slowly - not that it 

;; generates slow machine code.  So to get around this

;; if you compile from a separate Imprmoptu process you won't

;; effect any running temporal recursions in the "primary process"

;; or whatever other process you decide to run temporal recursions in

;; 

;; Compiling works (definec) from any process as the state

;; of the ICR is shared between all Impromptu processes.



;; Let's grunge up the instrument a little

;; and add some "instrument level" data (panning)

(definec instrument-kernel1

   (let ((pan 0.05)

         (inc 0.000005))

      ;; note that pan is state at the instrument level!

      ;; in other words all notes share pan

      (lambda ()         

         (let* ((losc (make-oscil 0.0))

                (rosc (make-oscil 0.0)))

            ;; but each note has its own losc and rosc 

            (lambda (freq:double time:double channel:double data:double*)               

               (set! pan (+ pan inc))

               (if (or (> pan .95) 

                       (< pan .05))

                   (set! inc (- 0.0 inc)))

               (let ((a (* .5 (lgamma (losc 0.5 freq))))

                     (b (rosc 0.9 (* 0.5 freq))))

                  ;; pan signal a

                  (if (= channel 0.0)

                      (+ b (* pan a))

                      (+ b (* (- 1.0 pan) a)))))))))




;; ok for our final trick just to check that the fxkernel 

;; is still working by adding a delay 


;; First I'm going to make a helper function

;; for a simple delay line

(definec delay-line 

   (lambda (size)

      ;; first make an 'size' length delay line      

      (let ((line (make-array size double)))

         ;; zero out delay line

         (dotimes (i size) (array-set! line i 0.0))

         ;; return delay-line closure

         (lambda (time x:double decay)

            ;; get our time into the delay line

            ;; as a module of time

            (let* ((pos (modulo (dtoi64 time) size))

                   (result (* decay (array-ref line pos))))

               (array-set! line pos (+ x result))

               result)))))


;; now use delay-line in fxkernel

;; different delay lengths for left and right

(definec fxkernel1

   (let ((dell (delay-line 14700))

         (delr (delay-line 11025)))

      (lambda (sample-in:double time:double channel data:double*)

         (if (= channel 0.0)

             (* 0.8 (dell time sample-in 0.5))

             (* 1.0 (delr time sample-in 0.5))))))