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@74
|
23 (let ((tempi (or (get-applicable-tempi object1 object2)
|
d@74
|
24 (signal 'undefined-action
|
d@74
|
25 :operation 'beats-to-seconds
|
d@74
|
26 :datatype 'constituent)))
|
d@36
|
27 (s 0))
|
d@36
|
28 (dolist (tempo tempi (/ s 1000000))
|
d@120
|
29 (incf s (if (disjoint tempo object1)
|
d@120
|
30 0
|
d@120
|
31 (* (duration (period-intersection tempo object1))
|
d@120
|
32 (amuse:microseconds-per-crotchet tempo)))))))
|
d@36
|
33 (defmethod beats-to-seconds ((object1 moment)
|
d@36
|
34 (object2 constituent))
|
d@120
|
35 (beats-to-seconds (time- (onset object1)
|
d@88
|
36 (make-moment 0))
|
d@36
|
37 object2))
|
d@36
|
38
|
d@33
|
39 ;; Not as simple as it seems - have to take into account numbering
|
d@33
|
40 ;; practices and leading silences in representations where bar number
|
d@33
|
41 ;; isn't part of the explicit structure.
|
d@33
|
42 (defgeneric bar-number (moment composition))
|
d@36
|
43 (defgeneric bar-onset (bar-number composition))
|
d@33
|
44
|
d@36
|
45 (defgeneric bass-note (anchored-period composition))
|
d@36
|
46
|
d@37
|
47
|
d@37
|
48 (defun levenshtein-distance (s1 s2 &key (insertion-cost 1)
|
d@37
|
49 (insertion-function) (deletion-cost 1)
|
d@37
|
50 (deletion-function) (substitution-cost 1)
|
d@37
|
51 (substitution-test #'equal) (substitution-function))
|
d@37
|
52 ;; This is an implementation of the Levenshtein distance measure
|
d@37
|
53 ;; based on the cliki asdf package, itself based on the wikipedia
|
d@37
|
54 ;; scheme example of the same algorithm. This version is generalised
|
d@37
|
55 ;; such that operations costs may take constant or calculated
|
d@37
|
56 ;; values. If insertion-function, deletion-function or
|
d@37
|
57 ;; substitution-test are specified, the applicable cost values are
|
d@37
|
58 ;; ignored and the function output is used instead.
|
d@37
|
59 (let* ((width (1+ (length s1)))
|
d@37
|
60 (height (1+ (length s2)))
|
d@37
|
61 (d (make-array (list height width))))
|
d@37
|
62 (dotimes (x width)
|
d@37
|
63 (setf (aref d 0 x) (* x deletion-cost)))
|
d@37
|
64 (dotimes (y height)
|
d@37
|
65 (setf (aref d y 0) (* y insertion-cost)))
|
d@37
|
66 (dotimes (x (length s1))
|
d@37
|
67 (dotimes (y (length s2))
|
d@37
|
68 (setf (aref d (1+ y) (1+ x))
|
d@37
|
69 (min (+ (if insertion-function
|
d@37
|
70 (apply insertion-function (elt s1 x))
|
d@37
|
71 insertion-cost)
|
d@37
|
72 (aref d y (1+ x)))
|
d@37
|
73 (+ (if deletion-function
|
d@37
|
74 (apply deletion-function (elt s2 y))
|
d@37
|
75 deletion-cost)
|
d@37
|
76 (aref d (1+ y) x))
|
d@37
|
77 (+ (aref d y x)
|
d@37
|
78 (if substitution-function
|
d@37
|
79 (apply substitution-function (list (elt s1 x) (elt s2 y)))
|
d@37
|
80 (if (apply substitution-test (list (elt s1 x) (elt s2 y)))
|
d@37
|
81 0
|
d@37
|
82 substitution-cost)))))))
|
d@41
|
83 (aref d (1- height) (1- width))))
|
d@41
|
84
|
d@41
|
85 ;;;;;;;;;;;;;;;;;;;;;;
|
d@41
|
86 ;;
|
d@41
|
87 ;; More experimental (from amuse-geerdes)
|
d@41
|
88 ;;
|
d@41
|
89 ;; Monody functions
|
d@41
|
90
|
d@41
|
91 (defun monodificate (composition)
|
d@41
|
92 (let ((events-bags) (latest-cut-off))
|
d@41
|
93 ;; - Filter out very short notes (<50ms)
|
d@41
|
94 ;; - If there are notes with the same onset time or a large
|
d@41
|
95 ;; proportion (e.g. >25%) of the notes in the segment have
|
d@41
|
96 ;; overlapping durations (of >75%), do for every simultaneous or
|
d@41
|
97 ;; overlapping pair of notes
|
d@41
|
98 ;; -- if one note is louder than the other note (e.g. quieter note
|
d@41
|
99 ;; <75% of louder one) select it as melody note
|
d@41
|
100 ;; -- else select note with higher pitch
|
d@41
|
101 ;; [FIXME: I'm ignoring overlaps for the time being]
|
d@41
|
102 ;; - For non-simultaneous notes with little overlap, set note ends
|
d@41
|
103 ;; to beginning of of onset of next (overlapping) note.
|
d@41
|
104
|
d@41
|
105 ;; STEP 1:
|
d@41
|
106 ;; `Filter out very short notes (<50ms)' and find `segments' for
|
d@41
|
107 ;; further filtering.
|
d@41
|
108 (sequence::dosequence (event composition)
|
d@41
|
109 (when (> (beats-to-seconds event composition)
|
d@41
|
110 1/20)
|
d@41
|
111 (if (or (not latest-cut-off)
|
d@41
|
112 (time> (onset event) latest-cut-off))
|
d@41
|
113 (push (list event) events-bags)
|
d@41
|
114 (push event (car events-bags)))
|
d@41
|
115 (when (or (not latest-cut-off)
|
d@41
|
116 (time> (cut-off event) latest-cut-off))
|
d@41
|
117 (setf latest-cut-off (cut-off event)))))
|
d@41
|
118 ;; Now check each segment for overlaps and
|
d@41
|
119 ;; simultanaieties. N.B. this is a reverse list of reversed
|
d@41
|
120 ;; lists.
|
d@41
|
121 (let ((adjusted-bags))
|
d@41
|
122 (dolist (events-bag events-bags)
|
d@41
|
123 (setf events-bag (reverse events-bag))
|
d@41
|
124 (let ((polyphonic-p (check-events-bag-for-polyphony events-bag)))
|
d@41
|
125 (cond
|
d@41
|
126 (polyphonic-p
|
d@41
|
127 (push (resolve-polyphony events-bag composition) adjusted-bags))
|
d@41
|
128 (t
|
d@41
|
129 (if (cdr events-bag)
|
d@41
|
130 (push (adjust-durations events-bag) adjusted-bags)
|
d@41
|
131 (push events-bag adjusted-bags))))))
|
d@41
|
132 (apply #'nconc adjusted-bags))))
|
d@41
|
133
|
d@41
|
134 (defun resolve-polyphony (event-list composition)
|
d@41
|
135 (do ((i 0 (1+ i)))
|
d@41
|
136 ((>= i (length event-list)) event-list)
|
d@41
|
137 (let ((event (nth i event-list)))
|
d@41
|
138 (do ((j (1+ i) (1+ j)))
|
d@41
|
139 ((or (>= j (length event-list))
|
d@41
|
140 (time>= (onset (nth j event-list))
|
d@41
|
141 (cut-off event))))
|
d@41
|
142 (let* ((event-2 (nth j event-list))
|
d@41
|
143 (inter-onset (time- (onset event-2) (onset event))))
|
d@41
|
144 (cond
|
d@41
|
145 ((and inter-onset
|
d@41
|
146 (< (* 2 (duration inter-onset))
|
d@41
|
147 (duration event))
|
d@41
|
148 (< (* 2 (duration inter-onset))
|
d@41
|
149 (duration event-2))
|
d@41
|
150 (< (beats-to-seconds inter-onset composition)
|
d@41
|
151 1/8))
|
d@41
|
152 ;; This is clearly polyphony
|
d@41
|
153 (cond
|
d@41
|
154 ((significantly-louderp event-2 event)
|
d@41
|
155 ;; Take event-2
|
d@41
|
156 (setf event-list (remove event event-list))
|
d@41
|
157 (decf i)
|
d@41
|
158 (return))
|
d@41
|
159 ((significantly-louderp event event-2)
|
d@41
|
160 ;; Take event
|
d@41
|
161 (setf event-list (remove event-2 event-list))
|
d@41
|
162 (decf j))
|
d@41
|
163 ((pitch> event event-2)
|
d@41
|
164 ;; Take event
|
d@41
|
165 (setf event-list (remove event-2 event-list))
|
d@41
|
166 (decf j))
|
d@41
|
167 (t
|
d@41
|
168 ;; Take event-2
|
d@41
|
169 (setf event-list (remove event event-list))
|
d@41
|
170 (decf i)
|
d@41
|
171 (return))))
|
d@41
|
172 (t
|
d@41
|
173 (cond
|
d@41
|
174 ((substantially-louderp event-2 event)
|
d@41
|
175 ;; Take event-2
|
d@41
|
176 (setf event-list (remove event event-list))
|
d@41
|
177 (decf i)
|
d@41
|
178 (return))
|
d@41
|
179 ((substantially-louderp event event-2)
|
d@41
|
180 ;; Take event
|
d@41
|
181 (setf event-list (remove event-2 event-list))
|
d@41
|
182 (decf j))
|
d@41
|
183 (t
|
d@41
|
184 ;; Take both
|
d@41
|
185 (let ((event-overlap (period-intersection event event-2)))
|
d@41
|
186 (when event-overlap
|
d@41
|
187 (setf (duration event)
|
d@41
|
188 (duration (time- event-overlap event))))))))))))))
|
d@41
|
189
|
d@41
|
190 (defgeneric significantly-louderp (event1 event2)
|
d@41
|
191 ;; noticably louder
|
d@41
|
192 (:method (e1 e2) (declare (ignore e1 e2)) nil))
|
d@41
|
193
|
d@41
|
194 (defgeneric substantially-louderp (event1 event2)
|
d@41
|
195 ;; much louder
|
d@41
|
196 (:method (e1 e2) (declare (ignore e1 e2)) nil))
|
d@41
|
197
|
d@41
|
198 (defun adjust-durations (events-list)
|
d@41
|
199 (do* ((old-list events-list (cdr old-list))
|
d@41
|
200 (event (first old-list) (first old-list))
|
d@41
|
201 (event-2 (second old-list) (second old-list)))
|
d@41
|
202 ((not event-2) events-list)
|
d@41
|
203 (let ((event-overlap (period-intersection event event-2)))
|
d@41
|
204 (when event-overlap
|
d@41
|
205 (setf (duration event)
|
d@41
|
206 (duration (time- event-overlap event)))))))
|
d@41
|
207
|
d@41
|
208 (defun check-events-bag-for-polyphony (events-bag)
|
d@41
|
209 (let ((overlaps (make-array (length events-bag) :initial-element nil)))
|
d@41
|
210 (when (= (length events-bag) 1)
|
d@41
|
211 ;; obviously no overlaps
|
d@41
|
212 (return-from check-events-bag-for-polyphony nil))
|
d@41
|
213 (unless (= (length (remove-duplicates events-bag :test #'time=))
|
d@41
|
214 (length events-bag))
|
d@41
|
215 ;; Duplicated onsets
|
d@41
|
216 (return-from check-events-bag-for-polyphony 'T))
|
d@41
|
217 ;; Now for the main bit
|
d@41
|
218 (do* ((events events-bag (cdr events))
|
d@41
|
219 (i 0 (1+ i))
|
d@41
|
220 (event (car events) (car events)))
|
d@41
|
221 ((null (cdr events)))
|
d@41
|
222 (unless (and (aref overlaps i)
|
d@41
|
223 (= (aref overlaps i) 1))
|
d@41
|
224 ;; Would mean we already have a maximal value
|
d@41
|
225 ;; and don't need any more checks
|
d@41
|
226 (do* ((events-2 (cdr events) (cdr events-2))
|
d@41
|
227 (j (1+ i) (1+ j))
|
d@41
|
228 (event-2 (car events-2) (car events-2)))
|
d@41
|
229 ((null events-2))
|
d@41
|
230 (when (time>= (onset event-2) (cut-off event))
|
d@41
|
231 ;; So no more overlaps
|
d@41
|
232 (return))
|
d@41
|
233 (let ((shorter (if (duration< event event-2)
|
d@41
|
234 i
|
d@41
|
235 j))
|
d@41
|
236 (overlap (/ (duration (period-intersection event event-2))
|
d@41
|
237 (min (duration event) (duration event-2)))))
|
d@41
|
238 ;; only look at pairings for the shorter note. This can
|
d@41
|
239 ;; have odd side effects, but means we never
|
d@41
|
240 ;; under-represent an overlap (I think)
|
d@132
|
241 (when (>= overlap 3/4)
|
d@132
|
242 (return-from check-events-bag-for-polyphony T))
|
d@41
|
243 (when (or (not (aref overlaps shorter))
|
d@41
|
244 (>= overlap (aref overlaps shorter)))
|
d@41
|
245 (setf (aref overlaps shorter) overlap)
|
d@41
|
246 (when (and (= shorter i)
|
d@41
|
247 (= overlap 1))
|
d@41
|
248 ;; Maximum value - we can stop
|
d@41
|
249 (return)))))))
|
d@41
|
250 (let ((total 0) (overs 0))
|
d@41
|
251 (loop for i from 0 to (1- (length events-bag))
|
d@41
|
252 do (when (aref overlaps i)
|
d@41
|
253 (incf total)
|
d@132
|
254 (when (>= (aref overlaps i) 1/2)
|
d@41
|
255 (incf overs))))
|
d@41
|
256 (if (and (> total 0)
|
d@41
|
257 (>= (/ overs total)
|
d@41
|
258 1/4))
|
d@132
|
259 T
|
d@132
|
260 nil))))
|
d@47
|
261
|
d@47
|
262 (defgeneric inter-onset-intervals (composition &key rounding-divisor))
|
d@47
|
263 (defmethod inter-onset-intervals ((composition composition) &key (rounding-divisor 1/4))
|
d@47
|
264 ;; returns values - list inter-onset intervals in beats, modal i-o-i
|
d@47
|
265 ;; and i-o-is in seconds.
|
d@47
|
266 ;; ** Only makes sense for monodic music
|
d@47
|
267 ;; FIXME: Should this keep in objects or am I right to make numbers
|
d@47
|
268 ;; here?
|
d@47
|
269 ;; FIXME: Should I (do I) filter out 0s?
|
d@47
|
270 (let ((i-o-i-list) (i-o-i-secs-list) (prev)
|
d@65
|
271 (hits (make-array (1+ (/ 32 rounding-divisor)))))
|
d@47
|
272 (loop for event being the elements of composition
|
d@47
|
273 do (progn
|
d@47
|
274 (when prev
|
d@47
|
275 (let* ((i-o-i-period (inter-onset-interval prev event))
|
d@47
|
276 (i-o-i (duration i-o-i-period))
|
d@65
|
277 (i-o-i-secs (beats-to-seconds i-o-i-period composition)))
|
d@47
|
278 (when (= i-o-i-secs 0)
|
d@47
|
279 (format t "~D, ~D -- " (timepoint prev) (timepoint event)))
|
d@47
|
280 (push i-o-i i-o-i-list)
|
d@47
|
281 (push i-o-i-secs i-o-i-secs-list)
|
d@47
|
282 (when (< i-o-i 32)
|
d@47
|
283 ;; Not really interested in very long results for the
|
d@47
|
284 ;; modal value anyway.
|
d@47
|
285 (incf (aref hits (round i-o-i rounding-divisor))))))
|
d@47
|
286 (setf prev event)))
|
d@47
|
287 (let ((mode '(0 0)))
|
d@47
|
288 ;; we want the position of the highest mode
|
d@47
|
289 (loop for i downfrom (1- (length hits)) to 0
|
d@47
|
290 when (> (aref hits i) (car mode))
|
d@47
|
291 do (setf mode (list (aref hits i) i)))
|
d@47
|
292 (values (reverse i-o-i-list)
|
d@47
|
293 (* (cadr mode) rounding-divisor)
|
d@47
|
294 (reverse i-o-i-secs-list)))))
|
d@47
|
295
|
d@47
|
296 (defun pitch-interval-list (composition)
|
d@47
|
297 (let ((intervals)
|
d@47
|
298 (previous-event))
|
d@47
|
299 (sequence:dosequence (event composition (reverse intervals))
|
d@47
|
300 (when previous-event
|
d@47
|
301 (push (span (pitch- event previous-event))
|
d@47
|
302 intervals))
|
m@54
|
303 (setf previous-event event))))
|