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