;; aa-cell pitch class library
;; A collection of functions for working with pitch class 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))
(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))))
;; 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 . dorian))
(iii7 . (4 . dorian))
(iv . (5 . lydian))
(iv7 . (5 . lydian))
(v . (7 . mixolydian))
(v7 . (7 . mixolydian))
(vi . (9 . dorian))
(vi7 . (9 . dorian))
(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 (fmod 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 pc)
(let loop ((inc 0))
(cond ((pc:? (+ pitch inc) pc) (+ pitch inc))
((pc:? (- pitch inc) pc) (- pitch inc))
((< inc 7) (loop (+ inc 1)))
(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 (fmod 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 (fmod (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) (fmod value 12))
i
(loop (+ i 1) (cdr lst)))))))
;; retrograde list
(define retrograde reverse)
;; invert list paying no attention to key
(define 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 transpose
(lambda (val lst)
(map (lambda (i)
(+ i val))
lst)))
;; expand/contract list by factor paying no attention to key
(define 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)))))))
;; quantize the values of lst to pc
(define pc:quantize-list
(lambda (lst pc)
(map (lambda (i)
(pc:quantize i pc))
lst)))
;; invert the values of lst quantizing to pc
(define pc:invert
(lambda (lst pc . args)
(if (null? args)
(pc:quantize-list (invert lst) pc)
(pc:quantize-list (invert lst (car args)) pc))))
;; transpose the values of lst quantizing to pc
(define pc:transpose
(lambda (val lst pc)
(pc:quantize-list (transpose val lst) pc)))
;; expand/contract lst by factor quantizing to pc
(define pc:expand/contract
(lambda (lst factor pc)
(pc:quantize-list (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 (fmod (+ (cadr (assoc type *pc:chord->scale*)) root))
(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) (fmod (+ 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 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 (fmod (+ (car l) root) 12) newlst))))
(begin (print-notification "Chord type not found." chords) #f)))))