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

;; 

;; THE CATHEDRAL 

;;

;; An example that uses simple stochastic matrix

;; to play a chord progression with chords represented as

;; pitch classes.

;;

;; Try adding a path to the matrix offering a potential 

;; modulation to a new key and another path back again :)

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


(au:clear-graph)


;Load Instrument

(define organ (au:make-node "aumu" "dls " "appl"))

(define reverb (au:make-node "aufx" "mrev" "appl"))

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

(au:connect-node organ 0 reverb 0)

(au:update-graph)


; send pipe organ program change to audio unit 

(au:midi-out (now) organ *io:midi-pc* 0 19 0)


; set cathedral reverb settings 

(au:set-param (now) reverb 0 0 0 100)

(au:set-param (now) reverb 1 0 0 86)

(au:set-param (now) reverb 2 0 0 0.028)

(au:set-param (now) reverb 3 0 0 0.09)

(au:set-param (now) reverb 4 0 0 0.015)

(au:set-param (now) reverb 5 0 0 0.001)

(au:set-param (now) reverb 6 0 0 0.5)

(au:set-param (now) reverb 7 0 0 1.0)


;; define some chords in c major as pitch classes 

(define *cmaj* '(0 4 7))

(define *cmaj-sus* '(0 5 7))

(define *dmin* '(2 5 9))

(define *emin* '(4 7 11))

(define *fmaj* '(5 9 0))

(define *gmaj* '(7 11 2))

(define *amin* '(9 0 4))

(define *bdim* '(11 2 4))


;; define stochastic chord matrix

;; this can be easily extended into a multi depth

;; markov matrix by replacing the car values with lists.

(define maj-matrix

   '((*cmaj* . (*dmin* *fmaj* *gmaj* *gmaj* *amin* *amin* *cmaj* *cmaj*))

     (*cmaj-sus* . (*cmaj* *cmaj* *cmaj* *dmin*))                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          

     (*dmin* . (*gmaj* *gmaj* *dmin* *cmaj* *cmaj*))

     (*fmaj* . (*dmin* *gmaj* *gmaj* *dmin* *fmaj* *gmaj* *cmaj*))

     (*gmaj* . (*amin* *gmaj* *cmaj* *cmaj* *cmaj-sus*))

     (*amin* . (*gmaj* *fmaj* *fmaj* *fmaj* *dmin* *amin* *dmin* *dmin*))))


;; select random pitch from pitch class 

;; bounded by lower and upper (inclusive lower exclusive upper)

;;

;; arg 1: lower bound (inclusive) 

;; arg 2: upper bound (exclusive)

;; arg 3: pitch class

;;

;; returns -1 if no valid pitch was found 

;;

(define random-pc

   (lambda (lower upper pc)

      (if (null? pc) 

          -1

          (let loop ((val (random lower upper)) (count 0))

             (if (> count 50) 

                 -1                   

                 (if (memv (fmod val 12) pc) 

                     val

                     (loop (random lower upper) (+ count 1))))))))


;; creates a list of "number" pitches between "lower" and "upper" 

;; bounds from the given "pc".  a division of the bounds

;; by the number of elements requested breaks down the selection into

;; equal ranges from which each pitch is selected.

;; make-chord attempts to select pitches of all degrees of the pc.

;; it is possible for elements of the returned chord to be -1 if no 

;; possible pc is available for the given range. 

;;

;; arg1: lower bound (inclusive)

;; arg2: upper bound (exclusive)

;; arg3: number of pitches in chord 

;; arg4: pitch class 

;;

;; example: c7  

;; (make-chord 60 85 4 '(0 4 7 10)) => (60 70 76 79) 

;; 

(define make-chord

   (lambda (lower upper number pc)

      (let ((chord '()))

         (let loop ((l lower)

                    (u upper)

                    (n number)

                    (p pc))

            (if (< n 1) 

                (reverse chord) ; lowest pitch to highest pitch

                (let* ((range (- u l))

                       (gap (round (/ range n)))

                       (pitch (random-pc l (+ l gap) p)))

                   (if (< pitch 0) ; if new pitch is -1 try from whole pc

                       (set! chord (cons (random-pc l (+ l gap) pc) chord))

                       (set! chord (cons pitch chord)))

                   (loop (+ l gap)

                         u

                         (- n 1)

                         (if (> (length p) 1) 

                             (cl:remove (fmod (car chord) 12) p)

                             pc))))))))


;; chord progression with random lengths 

;; durations less than 2.0 to be 2 note chords

;; all others 5 note chords 

(define progression

   (lambda (time chord-sym)

      (let ((lgth (random '(0.5 0.5 0.5 0.5 1.0 1.0 1.5 2.0 2.0 4.0))))

         (map (lambda (p)

                 (play-note time organ 

                            p (+ 80 (random 20)) (* lgth *second* 0.9)))

              (make-chord (if (< lgth 2.0) 69 24) ; bottom of the chord 

                          80 ; top of the chord

                          (if (< lgth 2.0) 2 5) ; how many notes in the chord

                          (eval chord-sym))) ; pitch class to use for chord

         (callback (+ time (* lgth 40000)) progression 

                   (+ time (* lgth *second*))

                   (random (cdr (assoc chord-sym maj-matrix)))))))


(define stop

   (lambda ()

      (set! progression (lambda (time chord-sym) 'done))))


;; play organ 

(progression (now) '*cmaj*)