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@39
|
118 (if (= equation-b 0)
|
d@39
|
119 (make-array 12 :initial-element 0)
|
d@39
|
120 (do ((i 0 (1+ i)))
|
d@39
|
121 ((= i n) results-array)
|
d@39
|
122 (setf equation-t (- (* n (loop for j from 0 to (1- n)
|
d@39
|
123 sum (* (aref vector1 (mod (+ i j) n))
|
d@39
|
124 (aref vector2 j))))
|
d@39
|
125 equation-tr)
|
d@39
|
126 (aref results-array i) (/ equation-t equation-b))))))
|
d@36
|
127
|
d@36
|
128
|
d@36
|
129 (defparameter *krumhansl-schmuckler-major-key*
|
d@36
|
130 (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
|
131
|
d@36
|
132 (defparameter *krumhansl-schmuckler-minor-key*
|
d@36
|
133 (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
|
134
|
d@36
|
135 (defun krumhansl-key-finder (anchored-period composition
|
d@36
|
136 &key (major *krumhansl-schmuckler-major-key*)
|
d@36
|
137 (minor *krumhansl-schmuckler-minor-key*))
|
d@36
|
138 (let* ((key) (best-score -1)
|
d@36
|
139 (pitches (pitch-class-distribution anchored-period composition))
|
d@36
|
140 (majors (vector-correlation pitches major))
|
d@36
|
141 (minors (vector-correlation pitches minor)))
|
d@36
|
142 (loop for i from 0 to 11
|
d@36
|
143 do (when (> (aref majors i) best-score)
|
d@36
|
144 (setf key (list i :major)
|
d@36
|
145 best-score (aref majors i))))
|
d@36
|
146 (loop for i from 0 to 11
|
d@36
|
147 do (when (> (aref minors i) best-score)
|
d@36
|
148 (setf key (list i :minor)
|
d@36
|
149 best-score (aref minors i))))
|
d@36
|
150 key))
|
d@37
|
151
|
d@37
|
152 (defun levenshtein-distance (s1 s2 &key (insertion-cost 1)
|
d@37
|
153 (insertion-function) (deletion-cost 1)
|
d@37
|
154 (deletion-function) (substitution-cost 1)
|
d@37
|
155 (substitution-test #'equal) (substitution-function))
|
d@37
|
156 ;; This is an implementation of the Levenshtein distance measure
|
d@37
|
157 ;; based on the cliki asdf package, itself based on the wikipedia
|
d@37
|
158 ;; scheme example of the same algorithm. This version is generalised
|
d@37
|
159 ;; such that operations costs may take constant or calculated
|
d@37
|
160 ;; values. If insertion-function, deletion-function or
|
d@37
|
161 ;; substitution-test are specified, the applicable cost values are
|
d@37
|
162 ;; ignored and the function output is used instead.
|
d@37
|
163 (let* ((width (1+ (length s1)))
|
d@37
|
164 (height (1+ (length s2)))
|
d@37
|
165 (d (make-array (list height width))))
|
d@37
|
166 (dotimes (x width)
|
d@37
|
167 (setf (aref d 0 x) (* x deletion-cost)))
|
d@37
|
168 (dotimes (y height)
|
d@37
|
169 (setf (aref d y 0) (* y insertion-cost)))
|
d@37
|
170 (dotimes (x (length s1))
|
d@37
|
171 (dotimes (y (length s2))
|
d@37
|
172 (setf (aref d (1+ y) (1+ x))
|
d@37
|
173 (min (+ (if insertion-function
|
d@37
|
174 (apply insertion-function (elt s1 x))
|
d@37
|
175 insertion-cost)
|
d@37
|
176 (aref d y (1+ x)))
|
d@37
|
177 (+ (if deletion-function
|
d@37
|
178 (apply deletion-function (elt s2 y))
|
d@37
|
179 deletion-cost)
|
d@37
|
180 (aref d (1+ y) x))
|
d@37
|
181 (+ (aref d y x)
|
d@37
|
182 (if substitution-function
|
d@37
|
183 (apply substitution-function (list (elt s1 x) (elt s2 y)))
|
d@37
|
184 (if (apply substitution-test (list (elt s1 x) (elt s2 y)))
|
d@37
|
185 0
|
d@37
|
186 substitution-cost)))))))
|
d@41
|
187 (aref d (1- height) (1- width))))
|
d@41
|
188
|
d@41
|
189 ;;;;;;;;;;;;;;;;;;;;;;
|
d@41
|
190 ;;
|
d@41
|
191 ;; More experimental (from amuse-geerdes)
|
d@41
|
192 ;;
|
d@41
|
193 ;; Monody functions
|
d@41
|
194
|
d@41
|
195 (defun monodificate (composition)
|
d@41
|
196 (let ((events-bags) (latest-cut-off))
|
d@41
|
197 ;; - Filter out very short notes (<50ms)
|
d@41
|
198 ;; - If there are notes with the same onset time or a large
|
d@41
|
199 ;; proportion (e.g. >25%) of the notes in the segment have
|
d@41
|
200 ;; overlapping durations (of >75%), do for every simultaneous or
|
d@41
|
201 ;; overlapping pair of notes
|
d@41
|
202 ;; -- if one note is louder than the other note (e.g. quieter note
|
d@41
|
203 ;; <75% of louder one) select it as melody note
|
d@41
|
204 ;; -- else select note with higher pitch
|
d@41
|
205 ;; [FIXME: I'm ignoring overlaps for the time being]
|
d@41
|
206 ;; - For non-simultaneous notes with little overlap, set note ends
|
d@41
|
207 ;; to beginning of of onset of next (overlapping) note.
|
d@41
|
208
|
d@41
|
209 ;; STEP 1:
|
d@41
|
210 ;; `Filter out very short notes (<50ms)' and find `segments' for
|
d@41
|
211 ;; further filtering.
|
d@41
|
212 (sequence::dosequence (event composition)
|
d@41
|
213 (when (> (beats-to-seconds event composition)
|
d@41
|
214 1/20)
|
d@41
|
215 (if (or (not latest-cut-off)
|
d@41
|
216 (time> (onset event) latest-cut-off))
|
d@41
|
217 (push (list event) events-bags)
|
d@41
|
218 (push event (car events-bags)))
|
d@41
|
219 (when (or (not latest-cut-off)
|
d@41
|
220 (time> (cut-off event) latest-cut-off))
|
d@41
|
221 (setf latest-cut-off (cut-off event)))))
|
d@41
|
222 ;; Now check each segment for overlaps and
|
d@41
|
223 ;; simultanaieties. N.B. this is a reverse list of reversed
|
d@41
|
224 ;; lists.
|
d@41
|
225 (let ((adjusted-bags))
|
d@41
|
226 (dolist (events-bag events-bags)
|
d@41
|
227 (setf events-bag (reverse events-bag))
|
d@41
|
228 (let ((polyphonic-p (check-events-bag-for-polyphony events-bag)))
|
d@41
|
229 (cond
|
d@41
|
230 (polyphonic-p
|
d@41
|
231 (push (resolve-polyphony events-bag composition) adjusted-bags))
|
d@41
|
232 (t
|
d@41
|
233 (if (cdr events-bag)
|
d@41
|
234 (push (adjust-durations events-bag) adjusted-bags)
|
d@41
|
235 (push events-bag adjusted-bags))))))
|
d@41
|
236 (apply #'nconc adjusted-bags))))
|
d@41
|
237
|
d@41
|
238 (defun resolve-polyphony (event-list composition)
|
d@41
|
239 (do ((i 0 (1+ i)))
|
d@41
|
240 ((>= i (length event-list)) event-list)
|
d@41
|
241 (let ((event (nth i event-list)))
|
d@41
|
242 (do ((j (1+ i) (1+ j)))
|
d@41
|
243 ((or (>= j (length event-list))
|
d@41
|
244 (time>= (onset (nth j event-list))
|
d@41
|
245 (cut-off event))))
|
d@41
|
246 (let* ((event-2 (nth j event-list))
|
d@41
|
247 (inter-onset (time- (onset event-2) (onset event))))
|
d@41
|
248 (cond
|
d@41
|
249 ((and inter-onset
|
d@41
|
250 (< (* 2 (duration inter-onset))
|
d@41
|
251 (duration event))
|
d@41
|
252 (< (* 2 (duration inter-onset))
|
d@41
|
253 (duration event-2))
|
d@41
|
254 (< (beats-to-seconds inter-onset composition)
|
d@41
|
255 1/8))
|
d@41
|
256 ;; This is clearly polyphony
|
d@41
|
257 (cond
|
d@41
|
258 ((significantly-louderp event-2 event)
|
d@41
|
259 ;; Take event-2
|
d@41
|
260 (setf event-list (remove event event-list))
|
d@41
|
261 (decf i)
|
d@41
|
262 (return))
|
d@41
|
263 ((significantly-louderp event event-2)
|
d@41
|
264 ;; Take event
|
d@41
|
265 (setf event-list (remove event-2 event-list))
|
d@41
|
266 (decf j))
|
d@41
|
267 ((pitch> event event-2)
|
d@41
|
268 ;; Take event
|
d@41
|
269 (setf event-list (remove event-2 event-list))
|
d@41
|
270 (decf j))
|
d@41
|
271 (t
|
d@41
|
272 ;; Take event-2
|
d@41
|
273 (setf event-list (remove event event-list))
|
d@41
|
274 (decf i)
|
d@41
|
275 (return))))
|
d@41
|
276 (t
|
d@41
|
277 (cond
|
d@41
|
278 ((substantially-louderp event-2 event)
|
d@41
|
279 ;; Take event-2
|
d@41
|
280 (setf event-list (remove event event-list))
|
d@41
|
281 (decf i)
|
d@41
|
282 (return))
|
d@41
|
283 ((substantially-louderp event event-2)
|
d@41
|
284 ;; Take event
|
d@41
|
285 (setf event-list (remove event-2 event-list))
|
d@41
|
286 (decf j))
|
d@41
|
287 (t
|
d@41
|
288 ;; Take both
|
d@41
|
289 (let ((event-overlap (period-intersection event event-2)))
|
d@41
|
290 (when event-overlap
|
d@41
|
291 (setf (duration event)
|
d@41
|
292 (duration (time- event-overlap event))))))))))))))
|
d@41
|
293
|
d@41
|
294 (defgeneric significantly-louderp (event1 event2)
|
d@41
|
295 ;; noticably louder
|
d@41
|
296 (:method (e1 e2) (declare (ignore e1 e2)) nil))
|
d@41
|
297
|
d@41
|
298 (defgeneric substantially-louderp (event1 event2)
|
d@41
|
299 ;; much louder
|
d@41
|
300 (:method (e1 e2) (declare (ignore e1 e2)) nil))
|
d@41
|
301
|
d@41
|
302 (defun adjust-durations (events-list)
|
d@41
|
303 (do* ((old-list events-list (cdr old-list))
|
d@41
|
304 (event (first old-list) (first old-list))
|
d@41
|
305 (event-2 (second old-list) (second old-list)))
|
d@41
|
306 ((not event-2) events-list)
|
d@41
|
307 (let ((event-overlap (period-intersection event event-2)))
|
d@41
|
308 (when event-overlap
|
d@41
|
309 (setf (duration event)
|
d@41
|
310 (duration (time- event-overlap event)))))))
|
d@41
|
311
|
d@41
|
312 (defun check-events-bag-for-polyphony (events-bag)
|
d@41
|
313 (let ((overlaps (make-array (length events-bag) :initial-element nil)))
|
d@41
|
314 (when (= (length events-bag) 1)
|
d@41
|
315 ;; obviously no overlaps
|
d@41
|
316 (return-from check-events-bag-for-polyphony nil))
|
d@41
|
317 (unless (= (length (remove-duplicates events-bag :test #'time=))
|
d@41
|
318 (length events-bag))
|
d@41
|
319 ;; Duplicated onsets
|
d@41
|
320 (return-from check-events-bag-for-polyphony 'T))
|
d@41
|
321 ;; Now for the main bit
|
d@41
|
322 (do* ((events events-bag (cdr events))
|
d@41
|
323 (i 0 (1+ i))
|
d@41
|
324 (event (car events) (car events)))
|
d@41
|
325 ((null (cdr events)))
|
d@41
|
326 (unless (and (aref overlaps i)
|
d@41
|
327 (= (aref overlaps i) 1))
|
d@41
|
328 ;; Would mean we already have a maximal value
|
d@41
|
329 ;; and don't need any more checks
|
d@41
|
330 (do* ((events-2 (cdr events) (cdr events-2))
|
d@41
|
331 (j (1+ i) (1+ j))
|
d@41
|
332 (event-2 (car events-2) (car events-2)))
|
d@41
|
333 ((null events-2))
|
d@41
|
334 (when (time>= (onset event-2) (cut-off event))
|
d@41
|
335 ;; So no more overlaps
|
d@41
|
336 (return))
|
d@41
|
337 (let ((shorter (if (duration< event event-2)
|
d@41
|
338 i
|
d@41
|
339 j))
|
d@41
|
340 (overlap (/ (duration (period-intersection event event-2))
|
d@41
|
341 (min (duration event) (duration event-2)))))
|
d@41
|
342 ;; only look at pairings for the shorter note. This can
|
d@41
|
343 ;; have odd side effects, but means we never
|
d@41
|
344 ;; under-represent an overlap (I think)
|
d@41
|
345 (when (or (not (aref overlaps shorter))
|
d@41
|
346 (>= overlap (aref overlaps shorter)))
|
d@41
|
347 (setf (aref overlaps shorter) overlap)
|
d@41
|
348 (when (and (= shorter i)
|
d@41
|
349 (= overlap 1))
|
d@41
|
350 ;; Maximum value - we can stop
|
d@41
|
351 (return)))))))
|
d@41
|
352 (let ((total 0) (overs 0))
|
d@41
|
353 (loop for i from 0 to (1- (length events-bag))
|
d@41
|
354 do (when (aref overlaps i)
|
d@41
|
355 (incf total)
|
d@41
|
356 (when (>= (aref overlaps i) 3/4)
|
d@41
|
357 (incf overs))))
|
d@41
|
358 (if (and (> total 0)
|
d@41
|
359 (>= (/ overs total)
|
d@41
|
360 1/4))
|
d@41
|
361 'T
|
d@41
|
362 'nil)))) |