m@23
|
1 ;;; General purpose utilities
|
m@23
|
2
|
m@23
|
3 (cl:in-package #:amuse-utils)
|
m@23
|
4
|
d@33
|
5 ;; Booleans (for filters)
|
d@33
|
6 (defgeneric pitchedp (event)
|
d@33
|
7 (:method (e) (declare (ignore e)) nil))
|
d@33
|
8 (defmethod pitchedp ((event amuse:pitched-event))
|
d@33
|
9 T)
|
d@33
|
10 (defgeneric unpitchedp (event)
|
d@33
|
11 (:method (e) (not (pitchedp e))))
|
d@33
|
12
|
d@33
|
13 ;; Rhythm methods
|
d@33
|
14 (defgeneric crotchets-in-a-bar (time-signature))
|
d@33
|
15 (defmethod crotchets-in-a-bar ((time-signature basic-time-signature))
|
d@33
|
16 (let ((num (time-signature-numerator time-signature))
|
d@33
|
17 (den (time-signature-denominator time-signature)))
|
d@33
|
18 (* num (/ 4 den))))
|
d@33
|
19
|
d@36
|
20 (defgeneric beats-to-seconds (object1 object2))
|
d@36
|
21 (defmethod beats-to-seconds ((object1 anchored-period)
|
d@36
|
22 (object2 constituent))
|
d@36
|
23 (let ((tempi (get-applicable-tempi object1 object2))
|
d@36
|
24 (s 0))
|
d@36
|
25 (dolist (tempo tempi (/ s 1000000))
|
d@36
|
26 (incf s (* (duration (period-intersection tempo object1))
|
d@36
|
27 (amuse:microseconds-per-crotchet tempo))))))
|
d@36
|
28 (defmethod beats-to-seconds ((object1 moment)
|
d@36
|
29 (object2 constituent))
|
d@36
|
30 (beats-to-seconds (make-anchored-period 0
|
d@36
|
31 (timepoint object1))
|
d@36
|
32 object2))
|
d@36
|
33
|
d@36
|
34
|
d@33
|
35 ;; Pitch methods
|
d@33
|
36
|
d@33
|
37 (defgeneric sounding-events (anchored-period sequence))
|
d@33
|
38 (defmethod sounding-events ((anchored-period anchored-period)
|
d@33
|
39 (sequence composition))
|
d@33
|
40 (let ((sounding))
|
d@33
|
41 (sequence:dosequence (event sequence (reverse sounding))
|
d@33
|
42 (cond
|
d@33
|
43 ((time>= event (cut-off anchored-period))
|
d@33
|
44 (return-from sounding-events (reverse sounding)))
|
d@33
|
45 ((period-intersection anchored-period event)
|
d@33
|
46 (push event sounding))))))
|
d@33
|
47
|
d@33
|
48 (defgeneric midi-pitch-distribution (anchored-period composition))
|
d@33
|
49 (defmethod midi-pitch-distribution ((anchored-period anchored-period)
|
d@33
|
50 composition)
|
d@33
|
51 (let ((pitches (make-array 128 :initial-element 0)))
|
d@33
|
52 (dolist (event (remove-if #'unpitchedp (sounding-events anchored-period composition)) pitches)
|
d@33
|
53 (let ((overlap (period-intersection anchored-period event)))
|
d@33
|
54 (if overlap
|
d@33
|
55 (incf (aref pitches (midi-pitch-number event))
|
d@33
|
56 (duration overlap))
|
d@33
|
57 (if (= (duration event) 0)
|
d@33
|
58 (format t "~%Note ~D beats in has no duration" (timepoint event))
|
d@33
|
59 (error "This function has gone wrong - looking for overlaps that don't exist")))))))
|
d@33
|
60
|
d@33
|
61 (defgeneric pitch-class-distribution (anchored-period composition))
|
d@33
|
62 (defmethod pitch-class-distribution ((anchored-period anchored-period)
|
d@33
|
63 composition)
|
d@33
|
64 (let ((pitches (make-array 12 :initial-element 0)))
|
d@33
|
65 (dolist (event (remove-if #'unpitchedp (sounding-events anchored-period composition)) pitches)
|
d@33
|
66 (let ((overlap (period-intersection anchored-period event)))
|
d@33
|
67 (if overlap
|
d@33
|
68 (incf (aref pitches (pitch-class event))
|
d@33
|
69 (duration overlap))
|
d@33
|
70 (if (= (duration event) 0)
|
d@33
|
71 (format t "~%Note ~D beats in has no duration" (timepoint event))
|
d@33
|
72 (error "This function has gone wrong - looking for overlaps that don't exist")))))))
|
d@33
|
73
|
d@33
|
74 (defun normalised-midi-pitch-distribution (object1 object2)
|
d@33
|
75 (normalise-vector (midi-pitch-distribution object1 object2)))
|
d@33
|
76 (defun normalised-pitch-class-distribution (object1 object2)
|
d@33
|
77 (normalise-vector (pitch-class-distribution object1 object2)))
|
d@33
|
78 (defun normalise-vector (vector &optional (target-sum 1))
|
d@33
|
79 (let ((total (loop for i from 0 to (1- (length vector))
|
d@33
|
80 sum (aref vector i))))
|
d@33
|
81 (cond
|
d@33
|
82 ((= total target-sum)
|
d@33
|
83 vector)
|
d@33
|
84 ((= total 0)
|
d@33
|
85 (make-array (length vector)
|
d@33
|
86 :initial-element (/ target-sum (length vector))))
|
d@33
|
87 (t
|
d@33
|
88 (map 'vector #'(lambda (x) (* x (/ target-sum total))) vector)))))
|
d@33
|
89
|
d@33
|
90 ;; Not as simple as it seems - have to take into account numbering
|
d@33
|
91 ;; practices and leading silences in representations where bar number
|
d@33
|
92 ;; isn't part of the explicit structure.
|
d@33
|
93 (defgeneric bar-number (moment composition))
|
d@36
|
94 (defgeneric bar-onset (bar-number composition))
|
d@33
|
95
|
d@36
|
96 (defgeneric bass-note (anchored-period composition))
|
d@36
|
97
|
d@36
|
98 (defun vector-correlation (vector1 vector2)
|
d@36
|
99 ;; useful for Krumhansl-Schmukler-like calculations
|
d@36
|
100 (assert (= (length vector1) (length vector2)))
|
d@36
|
101 (let* ((n (length vector1))
|
d@36
|
102 (sum-x (loop for i from 0 to (1- n)
|
d@36
|
103 sum (aref vector1 i)))
|
d@36
|
104 (sum-y (loop for i from 0 to (1- n)
|
d@36
|
105 sum (aref vector2 i)))
|
d@36
|
106 (equation-bl (sqrt (- (* n
|
d@36
|
107 (loop for i from 0 to (1- n)
|
d@36
|
108 sum (expt (aref vector1 i) 2)))
|
d@36
|
109 (expt sum-x 2))))
|
d@36
|
110 (equation-br (sqrt (- (* n
|
d@36
|
111 (loop for i from 0 to (1- n)
|
d@36
|
112 sum (expt (aref vector2 i) 2)))
|
d@36
|
113 (expt sum-y 2))))
|
d@36
|
114 (equation-b (* equation-br equation-bl))
|
d@36
|
115 (equation-tr (* sum-x sum-y))
|
d@36
|
116 (equation-t 0)
|
d@36
|
117 (results-array (make-array n)))
|
d@36
|
118 (do ((i 0 (1+ i)))
|
d@36
|
119 ((= i n) results-array)
|
d@36
|
120 (setf equation-t (- (* n (loop for j from 0 to (1- n)
|
d@36
|
121 sum (* (aref vector1 (mod (+ i j) n))
|
d@36
|
122 (aref vector2 j))))
|
d@36
|
123 equation-tr)
|
d@36
|
124 (aref results-array i) (/ equation-t equation-b)))))
|
d@36
|
125
|
d@36
|
126
|
d@36
|
127 (defparameter *krumhansl-schmuckler-major-key*
|
d@36
|
128 (make-array 12 :initial-contents '(6.33 2.68 3.52 5.38 2.6 3.53 2.54 4.75 3.98 2.69 3.34 3.17)))
|
d@36
|
129
|
d@36
|
130 (defparameter *krumhansl-schmuckler-minor-key*
|
d@36
|
131 (make-array 12 :initial-contents '(6.35 2.23 3.48 2.33 4.38 4.09 2.52 5.19 2.39 3.66 2.29 2.88)))
|
d@36
|
132
|
d@36
|
133 (defun krumhansl-key-finder (anchored-period composition
|
d@36
|
134 &key (major *krumhansl-schmuckler-major-key*)
|
d@36
|
135 (minor *krumhansl-schmuckler-minor-key*))
|
d@36
|
136 (let* ((key) (best-score -1)
|
d@36
|
137 (pitches (pitch-class-distribution anchored-period composition))
|
d@36
|
138 (majors (vector-correlation pitches major))
|
d@36
|
139 (minors (vector-correlation pitches minor)))
|
d@36
|
140 (loop for i from 0 to 11
|
d@36
|
141 do (when (> (aref majors i) best-score)
|
d@36
|
142 (setf key (list i :major)
|
d@36
|
143 best-score (aref majors i))))
|
d@36
|
144 (loop for i from 0 to 11
|
d@36
|
145 do (when (> (aref minors i) best-score)
|
d@36
|
146 (setf key (list i :minor)
|
d@36
|
147 best-score (aref minors i))))
|
d@36
|
148 key))
|