annotate utils/utils.lisp @ 65:5b02163ade2a

inter-onset-intervals bug fix darcs-hash:20070703170032-f76cc-2d3105245bacbb8eb25a568ac8a710af1a07c7af.gz
author David Lewis <d.lewis@gold.ac.uk>
date Tue, 03 Jul 2007 18:00:32 +0100
parents 32314fefc706
children a98c2adfd420
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@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))))