annotate utils/utils.lisp @ 132:bfe5afcad183

fix to monodiser darcs-hash:20070820144432-f76cc-29675eafe225b078be656eef11a6bff9dba70b81.gz
author David Lewis <d.lewis@gold.ac.uk>
date Mon, 20 Aug 2007 15:44:32 +0100
parents d2f68cedf573
children 5e362d998f29
rev   line source
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))))