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