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