annotate utils/utils.lisp @ 134:5e362d998f29

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