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