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