annotate utils/utils.lisp @ 74:a98c2adfd420

initial segmentation idea darcs-hash:20070713100751-f76cc-2cc54f680199ee8f8e7f620c5d46d1e4f059baec.gz
author David Lewis <d.lewis@gold.ac.uk>
date Fri, 13 Jul 2007 11:07:51 +0100
parents 5b02163ade2a
children 8ea75cc8bc2c
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@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))))