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

;;

;; A collection of functions for working with pitch class sets

;; and interval sets

;;

;; A pitch class in this library is taken to be a

;; list of MIDI note values from the first octave (0-11)

;; from which other pitches are compared using modulo 12.

;; Therefore, 0 = C, 1 = C#, etc..

;;

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


; Define basic diatonic major

(define *pc:diatonic-major*

   '((i . (0 . ^))

     (i6 . (0 . ^6))

     (i64 . (0 . ^64))

     (i7 . (0 . ^7))

     (i- . (0 . -))

     (i-7 . (0 . -7))                 

     (n . (1 . ^)) ; neopolitan

     (n6 . (1 . ^6)) ; neopolitan

     (ii . (2 . -))

     (ii6 . (2 . -6))

     (ii7 . (2 . -7))                     

     (ii9 . (2 . -9))                 

     (ii^ . (2 . ^))

     (ii^7 . (2 . ^7))                                      

     (iii . (4 . -))

     (iii6 . (4 . -6))     

     (iii7 . (4 . -7))

     (iii^ . (4 . ^))

     (iii^7 . (4 . ^7))                 

     (iv . (5 . ^))

     (iv6 . (5 . ^6))

     (iv7 . (5 . ^7))

     (iv- . (5 . -))

     (iv-7 . (5 . -7))                 

     (v . (7 . ^))

     (v6 . (7 . ^6))

     (v7 . (7 . 7))

     (v- . (7 . -))

     (v-7 . (7 . -7))                 

     (vi . (9 . -))

     (vi6 . (9 . -6))

     (vi7 . (9 . -7))

     (vi^ . (9 . ^))

     (vi^7 . (9 . ^7))                 

     (viio . (11 . o))

     (viio7 . (11 . o7))

     (vii . (11 . o))

     (vii7 . (11 . -7b5))

     ))

;; Define basic diatonic minor

(define *pc:diatonic-minor*

   '((i . (0 . -))

     (i6 . (0 . -6))

     (i64 . (0 . -64))

     (i7 . (0 . -7))

     (i^ . (0 . ^))

     (i^6 . (0 . ^6))     

     (i^64 . (0 . ^64))          

     (i^7 . (0 . ^7))

     (n . (1 . ^)) ; neopolitan

     (n6 . (1 . ^6)) ; neopolitan     

     (ii . (2 . o))

     (ii6 . (2 . o6))     

     (ii7 . (2 . o7))                     

     (ii- . (2 . -))

     (ii-6 . (2 . -6))     

     (ii-7 . (2 . -7))                                      

     (ii^ . (2 . ^))

     (ii^7 . (2 . ^7))                                                       

     (iii . (3 . ^))

     (iii6 . (3 . ^6))     

     (iii7 . (3 . ^7))

     (iii- . (3 . -))

     (iii-6 . (3 . -6))     

     (iii-7 . (3 . -7))

     (iv . (5 . -))

     (iv6 . (5 . -6))     

     (iv7 . (5 . -7))

     (iv^ . (5 . ^))

     (iv^6 . (5 . ^6))     

     (iv^7 . (5 . ^7))                 

     (v . (7 . ^))

     (v^ . (7 . ^))     

     (v6 . (7 . ^6))     

     (v7 . (7 . 7))                 

     (v- . (7 . -))

     (v-6 . (7 . -6))     

     (v-6 . (7 . -6))     

     (v-7 . (7 . -))

     (vi . (8 . ^))

     (vi6 . (8 . ^6))     

     (vi7 . (8 . ^7))

     (vi- . (8 . -))

     (vi-6 . (8 . -6))     

     (vi-7 . (8 . -7))                 

     (vii . (10 . ^))

     (vii6 . (10 . ^6))     

     (vii7 . (10 . ^7))

     (viio . (11 . o)) ;raised 7 (dim)

     (viio6 . (11 . o6)) ;raised 7 (dim)     

     (viio7 . (11 . o7)) ; raised 7 (dim)

     ))


;; various scales defined as pc sets

(define *pc:scales*

   '((pentatonic . (2 2 3 2))

     (wholetone . (2 2 2 2 2))

     (chromatic . (1 1 1 1 1 1 1 1 1 1 1))

     (octatonic . (2 1 2 1 2 1 2))                      

     (messiaen1 . (2 2 2 2 2))                                            

     (messiaen2 . (2 1 2 1 2 1 2))                                            

     (messiaen3 . (2 1 1 2 1 1 2 1))

     (messiaen4 . (1 1 3 1 1 1 3))

     (messiaen5 . (1 4 1 1 4))

     (messiaen6 . (2 2 1 1 2 2 1))

     (messiaen7 . (1 1 1 2 1 1 1 1 2))

     (ionian . (2 2 1 2 2 2))

     (dorian . (2 1 2 2 2 1))

     (phrygian . (1 2 2 2 1 2))

     (lydian . (2 2 2 1 2 2))

     (lydian-mixolydian . (2 1 2 1 2 1 2))

     (mixolydian . (2 2 1 2 2 1))

     (aeolian . (2 1 2 2 1 2))

     (locrian . (1 2 2 1 2 2))))


; Define basic chord symbols

(define *pc:chord-syms*

   '((^ . (0 4 7))

     (^sus . (0 5 7))

     (^6 . (4 7 0))

     (^64 . (7 0 4))

     (^7 . (0 4 7 11))          

     (^65 . (4 7 11 0))

     (^43 . (7 11 0 4))

     (^42 . (11 0 4 7))

     (^2 . (11 0 4 7))     

     (^7#4 . (0 4 7 11 6))     

     (^9 . (0 4 7 11 2))

     (7 . (0 4 7 10))

     (9 . (0 4 7 10 2))

     (65 . (4 7 10 0))

     (43 . (7 10 0 4))

     (2 . (10 0 4 7))

     (42 . (10 0 4 7))     

     (- . (0 3 7))

     (-sus . (0 5 7))

     (-6 . (3 7 0))

     (-64 . (7 0 3))     

     (-7 . (0 3 7 10))

     (-65 . (3 7 10 0))

     (-43 . (7 10 0 3))

     (-42 . (10 0 3 7))

     (-2 . (10 0 3 7))                

     (-9 . (0 3 7 10 2))                           

     (o . (0 3 6))

     (o6 . (3 6 0))

     (o64 . (6 0 3))

     (o7 . (0 3 6 8))

     (o65 . (3 6 8 0))

     (o43 . (6 8 0 3))

     (o42 . (8 0 3 6))

     (o2 . (8 0 3 6))

     (-7b5 . (0 3 6 9))))



(define *pc:chord-syms-scales*

   '((^ . 'ionian)

     (^sus . 'mixolydian)

     (^6 . 'ionian)

     (^64 . 'ionian)

     (^7 . 'ionian)

     (^65 . 'ionian)

     (^43 . 'ionian)

     (^42 . 'ionian)

     (^2 . 'ionian)     

     (^7#4 . 'ionian)     

     (^9 . 'ionian)

     (7 . 'mixolydian)

     (9 . 'mixolydian)

     (65 . 'mixolydian)

     (43 . 'mixolydian)

     (2 . 'mixolydian)

     (42 . 'mixolydian)     

     (- . 'dorian)

     (-sus . 'mixolydian)

     (-6 . 'dorian)

     (-64 . 'dorian)     

     (-7 . 'dorian)

     (-65 . 'dorian)

     (-43 . 'dorian)

     (-42 . 'dorian)

     (-2 . 'dorian)                

     (-9 . 'dorian)                           

     (o . 'locrian)

     (o6 . 'locrian)

     (o64 . 'locrian)

     (o7 . 'locrian)

     (o65 . 'locrian)

     (o43 . 'locrian)

     (o42 . 'locrian)

     (o2 . 'locrian)

     (-7b5 . 'locrian)))



(define *pc:chord-syms-dict* (objc:list->nsdictionary *pc:chord-syms*))


;; returns a scale based on a chord (standard jazz translations)

(define *pc:chord->scale*

   '((i . (0 . ionian))

     (i7 . (0 . ionian))

     (ii . (2 . dorian))

     (ii7 . (2 . dorian))

     (ii9 . (2 . dorian))

     (iii . (4 . phrygian))

     (iii7 . (4 . phrygian))

     (iv . (5 . lydian))

     (iv7 . (5 . lydian))

     (v . (7 . mixolydian))

     (v7 . (7 . mixolydian))

     (vi . (9 . aeolian))

     (vi7 . (9 . aeolian))

     (vii . (11 . locrian))

     (vii7 . (11 . locrian))))


;; A predicate for calculating if pitch is in pc

;;

;; arg 1: pitch to check against pc

;; arg 2: pc to check pitch against

;; 

;; retuns true or false

;;

(define pc:?

   (lambda (pitch pc)

      (if (list? (member (modulo pitch 12) pc))

          #t

          #f)))



;; quantize pc

;; Always slelects a higher value before a lower value where distance is equal.

;;

;; arg 1: pitch to quantize to pc

;; arg 2: pc to quantize pitch against

;;

;; returns quntized pitch or #f if non available

;;

(define pc:quantize

   (lambda (pitch-in pc)

      (let loop ((inc 0)

                 (pitch (round pitch-in)))

         (cond ((pc:? (+ pitch inc) pc) (+ pitch inc))

               ((pc:? (- pitch inc) pc) (- pitch inc))

               ((< inc 7) (loop (+ inc 1) pitch))

               (else (print-notification "no pc value to quantize to" pitch pc)

                     #f)))))


;; quantize pc

;; Always slelects a lower value before a higher value where distance is equal.

;;

;; arg 1: pitch to quantize to pc

;; arg 2: pc to quantize pitch against

;;

;; returns quntized pitch or #f if non available

;;

(define pc:quantize-low

   (lambda (pitch-in pc)

      (let loop ((inc 0)

                 (pitch (round pitch-in)))

         (cond ((pc:? (- pitch inc) pc) (- pitch inc))

               ((pc:? (+ pitch inc) pc) (+ pitch inc))

               ((< inc 7) (loop (+ inc 1) pitch))

               (else (print-notification "no pc value to quantize to" pitch pc)

                     #f)))))



;; 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 pc:random

   (lambda (lower upper pc)

      (if (null? pc) 

          -1

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

             (if (> count 50) 

                 -1                   

                 (if (memv (modulo val 12) pc) 

                     val

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


;; select pitch from pitch class relative to a given pitch

;; 

;; 1st: bass pitch

;; 2nd: pc relationship to bass pitch (max is abs 7) 

;; 3rd: pitch class  

;;

;; example: 

;; (pc:relative 64 -2 '(0 2 4 5 7 9 11)) => 60

;; (pc:relative 69 3 '(0 2 4 5 7 9 11)) => 74 

;; 

(define pc:relative

   (lambda (pitch i pc)

      (if (= i 0) pitch

          (let ((inc (if (negative? i) - +)))

             (let loop ((p (inc pitch 1)) (cnt 0))

                (if (pc:? p pc) (set! cnt (inc cnt 1)))

                (if (= cnt i) p

                    (loop (inc p 1) cnt)))))))


;; pc:make-chord

;; 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. 

;; non-deterministic (i.e. result can vary each time)

;;

;; arg1: lower bound (inclusive)

;; arg2: upper bound (exclusive)

;; arg3: number of pitches in chord 

;; arg4: pitch class 

;;

;; example: c7  

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

;; 

(define pc:make-chord

   (lambda (lower upper number pc)

      (let ((chord '()))

         (let loop ((l lower)

                    (u upper)

                    (n number)

                    (p pc))

            (if (< n 1) 

                (cl:sort (cl:remove -1 chord) <) ; lowest pitch to highest pitch remove -1s

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

                       (gap (round (/ range n)))

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

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

                       (set! chord (cons (pc:random lower upper p) chord))

                       (set! chord (cons pitch chord)))

                   (loop (+ l gap)

                         u

                         (- n 1)

                         (if (> (length p) 1) 

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

                             pc))))))))


;; Returns a scale degree of a given value (pitch) based on a pc

(define pc:degree

   (lambda (value pc)

      (let loop ((i 1)

                 (lst pc))

         (if (null? lst)

             (begin (print-notification "pitch not in pc") -1)

             (if (= (car lst) (modulo value 12))

                 i

                 (loop (+ i 1) (cdr lst)))))))



;; quantize the values of lst to pc

(define pc:quantize-list

   (lambda (lst pc)

      (map (lambda (i)

              (pc:quantize i pc))

           lst)))



;; retrograde list

(define ivl:retrograde reverse)


;; invert list paying no attention to key

(define ivl:invert

   (lambda (lst . args)      

      (let ((pivot (if (null? args)

                       (car lst)

                       (car args))))

         (cons (car lst) (map (lambda (i)

                                 (- pivot (- i pivot)))

                              (cdr lst))))))


;; transpose list paying no attention to key

(define ivl:transpose

   (lambda (val lst)

      (map (lambda (i)

              (+ i val))

           lst))) 


;; expand/contract list by factor paying no attention to key

(define ivl:expand/contract

   (lambda (lst factor)

      (cons (car lst)

            (let loop ((old (car lst))

                       (l (cdr lst))

                       (current (car lst))

                       (newlst '()))

               (if (null? l)

                   (reverse newlst)

                   (loop (car l)

                         (cdr l)

                         (+ current (* factor (- (car l) old)))

                         (cons (real->integer (+ current (* factor (- (car l) old))))

                               newlst)))))))



;; invert the values of lst quantizing to pc

(define pc:invert

   (lambda (lst pc . args)

      (if (null? args)

          (pc:quantize-list (ivl:invert lst) pc)

          (pc:quantize-list (ivl:invert lst (car args)) pc))))   


;; transpose the values of lst quantizing to pc

(define pc:transpose

   (lambda (val lst pc)

      (pc:quantize-list (ivl:transpose val lst) pc)))


;; expand/contract lst by factor quantizing to pc

(define ivl:expand/contract

   (lambda (lst factor pc)

      (pc:quantize-list (ivl:expand/contract lst factor) pc)))


;; returns a scale type based on a chord type (basic jazz modal theory)

(define pc:chord->scale

   (lambda (root type)

      (pc:scale (modulo (+ (cadr (assoc type *pc:chord->scale*)) root) 12)

                (cddr (assoc type *pc:chord->scale*)))))


;; returns a scale type based on a given root

(define pc:scale

   (lambda (root type)

      (if (assoc type *pc:scales*)

          (let loop ((l (cdr (assoc type *pc:scales*)))

                     (current root)

                     (newlst '()))

             (if (null? l)

                 (reverse (cons current newlst))

                 (loop (cdr l) (modulo (+ current (car l)) 12) (cons current newlst))))

          (begin (print-notification "Scale type not found." *pc:scales*) #f))))



;; returns a chord following basic diatonic harmony rules

;; based on root (0 for C etc.) maj/min ('- or '^) and degree (i-vii) 

(define pc:diatonic

   (lambda (root maj-min degree)   

      (let ((val (assoc degree     

                        (if (equal? '^ maj-min)

                            *pc:diatonic-major*

                            *pc:diatonic-minor*))))

         (pc:chord (modulo (+ root (cadr val)) 12) (cddr val)))))



;; returns a chord following basic diatonic harmony rules

;; based on root (0 for C etc.) maj/min ('- or '^) and degree (i-vii) 

(define pc:diatonic-dict

   (lambda (root maj-min degree)   

      (let ((val (objc->list (objc:call (if (equal? '^ maj-min)

                                            *pc:diatonic-major-dict*

                                            *pc:diatonic-minor-dict*)

                                        "objectForKey:" (symbol->string degree)))))

         (pc:chord (modulo (+ root (cadr val)) 12) (cddr val)))))



;; returns a chord given a root and type

;; see *pc:chord-syms* for currently available types

;; 

;; e.g. (pc:chord 0 '^7)  => '(0 4 7 11)

(define pc:chord

   (lambda (root type)

      (let ((chord (assoc type *pc:chord-syms*)))

         (if chord

             (let loop ((l (cdr chord))

                        (newlst '()))

                (if (null? l)

                    (reverse newlst)

                    (loop (cdr l) (cons (modulo (+ (car l) root) 12) newlst))))

             (begin (print-notification "Chord type not found." chord) #f)))))



;; returns chord options for root in maj-min key of pc

;;

;; e.g. (pc:chord-options 0 '^ (pc:scale 0 'ionian)) => ((0 4 7) (0 4 7 11) (0 5 7) (0 4 7 11 2) (0 4 7 11 6))

(define pc:chord-options

   (lambda (root maj-min pc)

      (let ((major7 '(^ ^7 ^sus ^9 ^7#4))

            (dom7 '(^ 7 ^sus 9))

            (minor7 '(- -7 -sus -9))

            (dim7 '(o -7b5 o7))

            (degree (pc:degree root pc)))

         (map (lambda (sym)

                 (pc:chord root sym))

              (if (equal? maj-min '^)

                  (case degree

                        ((-1) '())

                        ((1 4) major7)

                        ((5) dom7)

                        ((2 3 6) minor7)

                        ((7) dim7))

                  (case degree

                        ((-1) '())

                        ((1 4 6) minor7)

                        ((3) major7)

                        ((5) (append minor7 dom7))

                        ((2) dim7)

                        ((7) (append dom7 dim7))))))))



;; make a chord that is fixed at either the 'top or the 'bottom

;; where fixed is as close as the chord allows to fix-point

;; defaults to bottom

;;

;; (pc:make-chord-fixed 60 3 '(0 3 7))      => (60 63 67)

;; (pc:make-chord-fixed 60 3 '(0 3 7) 'top) => (51 55 60)

;;

(define pc:make-chord-fixed

   (lambda (fix-point number pc . args)

      (if (< number 1) 

          '()

          (let* ((fixd (modulo fix-point 12))

                 (place (if (null? args) 'bottom (car args)))

                 (bass (- fix-point fixd)) 

                 (chord (if (eq? place 'bottom) pc (reverse pc)))            

                 (v (map (lambda (pc) (abs (- fixd pc))) chord))

                 (minim (apply min v))

                 (start (- (length v) (length (member minim v)))))

             (let loop ((i 0)

                        (new-lst '())

                        (bb bass)

                        (lst (list-tail chord start)))

                (if (< i number)

                    (loop (+ i 1)

                          (cons (+ bb (car lst)) new-lst)

                          (if (null? (cdr lst)) 

                              ((if (null? args) + -) bb 12)

                              bb)

                          (if (null? (cdr lst))

                              chord

                              (cdr lst)))

                    (if (eq? place 'bottom) 

                        (reverse new-lst)

                        new-lst)))))))



(define pc:distance-of-chord

   (lambda (chd pc)

      (apply + (map (lambda (p)

                       (pc:distance p pc))

                    chd))))


;; distance between pitch and a pc

(define pc:distance

   (lambda (pitch pc)

      (let ((p (modulo pitch 12)))

         (car (cl:sort (map (lambda (class)

                               (let ((val (abs (- p class))))

                                  (abs (if (< val (- 12 val)) val (- 12 val)))))

                            pc) <)))))


;; returns the pitch in plst that is closest to the pc set

;; if multiple pitches in plst are the closest return the first

(define pc:find-closest

   (lambda (plst pc)

      (cdar (cl:sort (map (lambda (p)

                            (cons (pc:distance p pc) p))

                         plst)                    

                     (lambda (a b) (if (< (car a) (car b)) #t #f))))))



;; find shortest part movement from chord to pc

(define pc:move-chord

   (lambda (chord pc . args)

      (let loop ((pci pc)

                 (chda chord)

                 (chdb '()))

         (if (null? pci) (set! pci pc))

         (if (null? chda)

             (cl:sort chdb <)

             (let* ((match (pc:find-closest chda pci))

                    (new-pitch (if (> (random) .5)

                                   (pc:quantize-low match pci)

                                   (pc:quantize match pci))))

                (loop (remove-first (modulo new-pitch 12) pci)

                      (remove-first match chda)

                      (cons new-pitch chdb)))))))



;; returns the type of scale given a pc or the pc itself

;; if no known scale is found

(define pc:scale-from-pc

   (lambda (pc)

      (let ((scale (let loop ((scale pc)

                              (ivls '()))

                      (if (null? (cdr scale))

                          (reverse ivls)

                          (loop (cdr scale) (cons (let ((v (- (cadr scale) (car scale))))

                                                     (if (< v 0)

                                                         (+ 12 v)

                                                         v)) ivls))))))

         (let check ((scales *pc:scales*))

            (if (null? scales) 

                pc

                (if (equal? scale (cdar scales))    

                    (caar scales)

                    (check (cdr scales))))))))


;; returns a pc-set based on a list of intervals

;; plst is the seed for the progression

;; usually this will be a list with one element

(define pc:from-intervals

   (lambda (plst intervals)

      (if (null? intervals)

          (reverse plst)

          (pc:from-intervals (cons (modulo (+ (car plst)

                                              (car ivls)) 

                                           12)

                                   plst)

                             (cdr intervals)))))


;returns a pc-set based on a list of steps a beginning pitch class and a pc-set

(define pc:from-steps

   (lambda (pitch steps pc)

      (let loop ((slst steps)

                 (plst (list pitch)))

         (if (null? slst)

             (reverse plst)

             (loop (cdr slst) 

                   (cons (modulo (pc:relative (car plst) (car slst) pc) 7) plst))))))


;; attempts to return a reasonable scale based on the chord and root provided

(define pc:scale-from-chord

   (lambda (root chord)

      (let ((res (cl:find-if (lambda (v)

                                (equal? (car v) chord))

                             (map (lambda (scale)              

                                     (cons (cl:intersection chord scale) scale))

                                  (map (lambda (type)

                                          (pc:scale root type))

                                       '(ionian aeolian mixolydian lydian phrygian locrian 

                                                dorian lydian-mixolydian wholetone chromatic))))))

         (if (pair? res)

             (cdr res)

             chord))))

            


;; genereate a melody from a list of steps in a (pc) pitch class

(define pc:melody-by-step

   (lambda (starting-pitch steps pc . args)

      (if (null? steps)

          (reverse (car args))

          (if (null? args)

              (pc:melody-by-step starting-pitch steps pc (cons starting-pitch args))

              (pc:melody-by-step (pc:relative starting-pitch (car steps) pc) 

                                 (cdr steps)

                                 pc                                

                                 (cons (pc:relative starting-pitch (car steps) pc) (car args)))))))



;; generate a meldoy from a list of intervals

(define ivl:melody-by-ivl

   (lambda (starting-pitch ivls . args)

      (if (null? ivls)

          (reverse (car args))

          (if (null? args)

              (ivl:melody-by-ivl starting-pitch ivls (cons starting-pitch args))

              (ivl:melody-by-ivl (+ starting-pitch (car ivls)) (cdr ivls) 

                                 (cons (+ starting-pitch (car ivls)) (car args)))))))