annotate utils/utils.lisp @ 47:e3d86a0f25b3

n-gram features darcs-hash:20070615112417-f76cc-6cc8c9b58db4f04bf1793af6521cbb037dce485f.gz
author David Lewis <d.lewis@gold.ac.uk>
date Fri, 15 Jun 2007 12:24:17 +0100
parents 90abdf9adb60
children df1482ef96fe
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@36 34
d@33 35 ;; Pitch methods
d@33 36
d@33 37 (defgeneric sounding-events (anchored-period sequence))
d@33 38 (defmethod sounding-events ((anchored-period anchored-period)
d@33 39 (sequence composition))
d@33 40 (let ((sounding))
d@33 41 (sequence:dosequence (event sequence (reverse sounding))
d@33 42 (cond
d@33 43 ((time>= event (cut-off anchored-period))
d@33 44 (return-from sounding-events (reverse sounding)))
d@33 45 ((period-intersection anchored-period event)
d@33 46 (push event sounding))))))
d@33 47
d@33 48 (defgeneric midi-pitch-distribution (anchored-period composition))
d@33 49 (defmethod midi-pitch-distribution ((anchored-period anchored-period)
d@33 50 composition)
d@33 51 (let ((pitches (make-array 128 :initial-element 0)))
d@33 52 (dolist (event (remove-if #'unpitchedp (sounding-events anchored-period composition)) pitches)
d@33 53 (let ((overlap (period-intersection anchored-period event)))
d@33 54 (if overlap
d@33 55 (incf (aref pitches (midi-pitch-number event))
d@33 56 (duration overlap))
d@33 57 (if (= (duration event) 0)
d@33 58 (format t "~%Note ~D beats in has no duration" (timepoint event))
d@33 59 (error "This function has gone wrong - looking for overlaps that don't exist")))))))
d@33 60
d@33 61 (defgeneric pitch-class-distribution (anchored-period composition))
d@33 62 (defmethod pitch-class-distribution ((anchored-period anchored-period)
d@33 63 composition)
d@33 64 (let ((pitches (make-array 12 :initial-element 0)))
d@33 65 (dolist (event (remove-if #'unpitchedp (sounding-events anchored-period composition)) pitches)
d@33 66 (let ((overlap (period-intersection anchored-period event)))
d@33 67 (if overlap
d@33 68 (incf (aref pitches (pitch-class event))
d@33 69 (duration overlap))
d@33 70 (if (= (duration event) 0)
d@33 71 (format t "~%Note ~D beats in has no duration" (timepoint event))
d@33 72 (error "This function has gone wrong - looking for overlaps that don't exist")))))))
d@33 73
d@33 74 (defun normalised-midi-pitch-distribution (object1 object2)
d@33 75 (normalise-vector (midi-pitch-distribution object1 object2)))
d@33 76 (defun normalised-pitch-class-distribution (object1 object2)
d@33 77 (normalise-vector (pitch-class-distribution object1 object2)))
d@33 78 (defun normalise-vector (vector &optional (target-sum 1))
d@33 79 (let ((total (loop for i from 0 to (1- (length vector))
d@33 80 sum (aref vector i))))
d@33 81 (cond
d@33 82 ((= total target-sum)
d@33 83 vector)
d@33 84 ((= total 0)
d@33 85 (make-array (length vector)
d@33 86 :initial-element (/ target-sum (length vector))))
d@33 87 (t
d@33 88 (map 'vector #'(lambda (x) (* x (/ target-sum total))) vector)))))
d@33 89
d@33 90 ;; Not as simple as it seems - have to take into account numbering
d@33 91 ;; practices and leading silences in representations where bar number
d@33 92 ;; isn't part of the explicit structure.
d@33 93 (defgeneric bar-number (moment composition))
d@36 94 (defgeneric bar-onset (bar-number composition))
d@33 95
d@36 96 (defgeneric bass-note (anchored-period composition))
d@36 97
d@36 98 (defun vector-correlation (vector1 vector2)
d@36 99 ;; useful for Krumhansl-Schmukler-like calculations
d@36 100 (assert (= (length vector1) (length vector2)))
d@36 101 (let* ((n (length vector1))
d@36 102 (sum-x (loop for i from 0 to (1- n)
d@36 103 sum (aref vector1 i)))
d@36 104 (sum-y (loop for i from 0 to (1- n)
d@36 105 sum (aref vector2 i)))
d@36 106 (equation-bl (sqrt (- (* n
d@36 107 (loop for i from 0 to (1- n)
d@36 108 sum (expt (aref vector1 i) 2)))
d@36 109 (expt sum-x 2))))
d@36 110 (equation-br (sqrt (- (* n
d@36 111 (loop for i from 0 to (1- n)
d@36 112 sum (expt (aref vector2 i) 2)))
d@36 113 (expt sum-y 2))))
d@36 114 (equation-b (* equation-br equation-bl))
d@36 115 (equation-tr (* sum-x sum-y))
d@36 116 (equation-t 0)
d@36 117 (results-array (make-array n)))
d@39 118 (if (= equation-b 0)
d@39 119 (make-array 12 :initial-element 0)
d@39 120 (do ((i 0 (1+ i)))
d@39 121 ((= i n) results-array)
d@39 122 (setf equation-t (- (* n (loop for j from 0 to (1- n)
d@39 123 sum (* (aref vector1 (mod (+ i j) n))
d@39 124 (aref vector2 j))))
d@39 125 equation-tr)
d@39 126 (aref results-array i) (/ equation-t equation-b))))))
d@36 127
d@36 128
d@36 129 (defparameter *krumhansl-schmuckler-major-key*
d@36 130 (make-array 12 :initial-contents '(6.33 2.68 3.52 5.38 2.6 3.53 2.54 4.75 3.98 2.69 3.34 3.17)))
d@36 131
d@36 132 (defparameter *krumhansl-schmuckler-minor-key*
d@36 133 (make-array 12 :initial-contents '(6.35 2.23 3.48 2.33 4.38 4.09 2.52 5.19 2.39 3.66 2.29 2.88)))
d@36 134
d@36 135 (defun krumhansl-key-finder (anchored-period composition
d@36 136 &key (major *krumhansl-schmuckler-major-key*)
d@36 137 (minor *krumhansl-schmuckler-minor-key*))
d@36 138 (let* ((key) (best-score -1)
d@36 139 (pitches (pitch-class-distribution anchored-period composition))
d@36 140 (majors (vector-correlation pitches major))
d@36 141 (minors (vector-correlation pitches minor)))
d@36 142 (loop for i from 0 to 11
d@36 143 do (when (> (aref majors i) best-score)
d@36 144 (setf key (list i :major)
d@36 145 best-score (aref majors i))))
d@36 146 (loop for i from 0 to 11
d@36 147 do (when (> (aref minors i) best-score)
d@36 148 (setf key (list i :minor)
d@36 149 best-score (aref minors i))))
d@36 150 key))
d@37 151
d@37 152 (defun levenshtein-distance (s1 s2 &key (insertion-cost 1)
d@37 153 (insertion-function) (deletion-cost 1)
d@37 154 (deletion-function) (substitution-cost 1)
d@37 155 (substitution-test #'equal) (substitution-function))
d@37 156 ;; This is an implementation of the Levenshtein distance measure
d@37 157 ;; based on the cliki asdf package, itself based on the wikipedia
d@37 158 ;; scheme example of the same algorithm. This version is generalised
d@37 159 ;; such that operations costs may take constant or calculated
d@37 160 ;; values. If insertion-function, deletion-function or
d@37 161 ;; substitution-test are specified, the applicable cost values are
d@37 162 ;; ignored and the function output is used instead.
d@37 163 (let* ((width (1+ (length s1)))
d@37 164 (height (1+ (length s2)))
d@37 165 (d (make-array (list height width))))
d@37 166 (dotimes (x width)
d@37 167 (setf (aref d 0 x) (* x deletion-cost)))
d@37 168 (dotimes (y height)
d@37 169 (setf (aref d y 0) (* y insertion-cost)))
d@37 170 (dotimes (x (length s1))
d@37 171 (dotimes (y (length s2))
d@37 172 (setf (aref d (1+ y) (1+ x))
d@37 173 (min (+ (if insertion-function
d@37 174 (apply insertion-function (elt s1 x))
d@37 175 insertion-cost)
d@37 176 (aref d y (1+ x)))
d@37 177 (+ (if deletion-function
d@37 178 (apply deletion-function (elt s2 y))
d@37 179 deletion-cost)
d@37 180 (aref d (1+ y) x))
d@37 181 (+ (aref d y x)
d@37 182 (if substitution-function
d@37 183 (apply substitution-function (list (elt s1 x) (elt s2 y)))
d@37 184 (if (apply substitution-test (list (elt s1 x) (elt s2 y)))
d@37 185 0
d@37 186 substitution-cost)))))))
d@41 187 (aref d (1- height) (1- width))))
d@41 188
d@41 189 ;;;;;;;;;;;;;;;;;;;;;;
d@41 190 ;;
d@41 191 ;; More experimental (from amuse-geerdes)
d@41 192 ;;
d@41 193 ;; Monody functions
d@41 194
d@41 195 (defun monodificate (composition)
d@41 196 (let ((events-bags) (latest-cut-off))
d@41 197 ;; - Filter out very short notes (<50ms)
d@41 198 ;; - If there are notes with the same onset time or a large
d@41 199 ;; proportion (e.g. >25%) of the notes in the segment have
d@41 200 ;; overlapping durations (of >75%), do for every simultaneous or
d@41 201 ;; overlapping pair of notes
d@41 202 ;; -- if one note is louder than the other note (e.g. quieter note
d@41 203 ;; <75% of louder one) select it as melody note
d@41 204 ;; -- else select note with higher pitch
d@41 205 ;; [FIXME: I'm ignoring overlaps for the time being]
d@41 206 ;; - For non-simultaneous notes with little overlap, set note ends
d@41 207 ;; to beginning of of onset of next (overlapping) note.
d@41 208
d@41 209 ;; STEP 1:
d@41 210 ;; `Filter out very short notes (<50ms)' and find `segments' for
d@41 211 ;; further filtering.
d@41 212 (sequence::dosequence (event composition)
d@41 213 (when (> (beats-to-seconds event composition)
d@41 214 1/20)
d@41 215 (if (or (not latest-cut-off)
d@41 216 (time> (onset event) latest-cut-off))
d@41 217 (push (list event) events-bags)
d@41 218 (push event (car events-bags)))
d@41 219 (when (or (not latest-cut-off)
d@41 220 (time> (cut-off event) latest-cut-off))
d@41 221 (setf latest-cut-off (cut-off event)))))
d@41 222 ;; Now check each segment for overlaps and
d@41 223 ;; simultanaieties. N.B. this is a reverse list of reversed
d@41 224 ;; lists.
d@41 225 (let ((adjusted-bags))
d@41 226 (dolist (events-bag events-bags)
d@41 227 (setf events-bag (reverse events-bag))
d@41 228 (let ((polyphonic-p (check-events-bag-for-polyphony events-bag)))
d@41 229 (cond
d@41 230 (polyphonic-p
d@41 231 (push (resolve-polyphony events-bag composition) adjusted-bags))
d@41 232 (t
d@41 233 (if (cdr events-bag)
d@41 234 (push (adjust-durations events-bag) adjusted-bags)
d@41 235 (push events-bag adjusted-bags))))))
d@41 236 (apply #'nconc adjusted-bags))))
d@41 237
d@41 238 (defun resolve-polyphony (event-list composition)
d@41 239 (do ((i 0 (1+ i)))
d@41 240 ((>= i (length event-list)) event-list)
d@41 241 (let ((event (nth i event-list)))
d@41 242 (do ((j (1+ i) (1+ j)))
d@41 243 ((or (>= j (length event-list))
d@41 244 (time>= (onset (nth j event-list))
d@41 245 (cut-off event))))
d@41 246 (let* ((event-2 (nth j event-list))
d@41 247 (inter-onset (time- (onset event-2) (onset event))))
d@41 248 (cond
d@41 249 ((and inter-onset
d@41 250 (< (* 2 (duration inter-onset))
d@41 251 (duration event))
d@41 252 (< (* 2 (duration inter-onset))
d@41 253 (duration event-2))
d@41 254 (< (beats-to-seconds inter-onset composition)
d@41 255 1/8))
d@41 256 ;; This is clearly polyphony
d@41 257 (cond
d@41 258 ((significantly-louderp event-2 event)
d@41 259 ;; Take event-2
d@41 260 (setf event-list (remove event event-list))
d@41 261 (decf i)
d@41 262 (return))
d@41 263 ((significantly-louderp event event-2)
d@41 264 ;; Take event
d@41 265 (setf event-list (remove event-2 event-list))
d@41 266 (decf j))
d@41 267 ((pitch> event event-2)
d@41 268 ;; Take event
d@41 269 (setf event-list (remove event-2 event-list))
d@41 270 (decf j))
d@41 271 (t
d@41 272 ;; Take event-2
d@41 273 (setf event-list (remove event event-list))
d@41 274 (decf i)
d@41 275 (return))))
d@41 276 (t
d@41 277 (cond
d@41 278 ((substantially-louderp event-2 event)
d@41 279 ;; Take event-2
d@41 280 (setf event-list (remove event event-list))
d@41 281 (decf i)
d@41 282 (return))
d@41 283 ((substantially-louderp event event-2)
d@41 284 ;; Take event
d@41 285 (setf event-list (remove event-2 event-list))
d@41 286 (decf j))
d@41 287 (t
d@41 288 ;; Take both
d@41 289 (let ((event-overlap (period-intersection event event-2)))
d@41 290 (when event-overlap
d@41 291 (setf (duration event)
d@41 292 (duration (time- event-overlap event))))))))))))))
d@41 293
d@41 294 (defgeneric significantly-louderp (event1 event2)
d@41 295 ;; noticably louder
d@41 296 (:method (e1 e2) (declare (ignore e1 e2)) nil))
d@41 297
d@41 298 (defgeneric substantially-louderp (event1 event2)
d@41 299 ;; much louder
d@41 300 (:method (e1 e2) (declare (ignore e1 e2)) nil))
d@41 301
d@41 302 (defun adjust-durations (events-list)
d@41 303 (do* ((old-list events-list (cdr old-list))
d@41 304 (event (first old-list) (first old-list))
d@41 305 (event-2 (second old-list) (second old-list)))
d@41 306 ((not event-2) events-list)
d@41 307 (let ((event-overlap (period-intersection event event-2)))
d@41 308 (when event-overlap
d@41 309 (setf (duration event)
d@41 310 (duration (time- event-overlap event)))))))
d@41 311
d@41 312 (defun check-events-bag-for-polyphony (events-bag)
d@41 313 (let ((overlaps (make-array (length events-bag) :initial-element nil)))
d@41 314 (when (= (length events-bag) 1)
d@41 315 ;; obviously no overlaps
d@41 316 (return-from check-events-bag-for-polyphony nil))
d@41 317 (unless (= (length (remove-duplicates events-bag :test #'time=))
d@41 318 (length events-bag))
d@41 319 ;; Duplicated onsets
d@41 320 (return-from check-events-bag-for-polyphony 'T))
d@41 321 ;; Now for the main bit
d@41 322 (do* ((events events-bag (cdr events))
d@41 323 (i 0 (1+ i))
d@41 324 (event (car events) (car events)))
d@41 325 ((null (cdr events)))
d@41 326 (unless (and (aref overlaps i)
d@41 327 (= (aref overlaps i) 1))
d@41 328 ;; Would mean we already have a maximal value
d@41 329 ;; and don't need any more checks
d@41 330 (do* ((events-2 (cdr events) (cdr events-2))
d@41 331 (j (1+ i) (1+ j))
d@41 332 (event-2 (car events-2) (car events-2)))
d@41 333 ((null events-2))
d@41 334 (when (time>= (onset event-2) (cut-off event))
d@41 335 ;; So no more overlaps
d@41 336 (return))
d@41 337 (let ((shorter (if (duration< event event-2)
d@41 338 i
d@41 339 j))
d@41 340 (overlap (/ (duration (period-intersection event event-2))
d@41 341 (min (duration event) (duration event-2)))))
d@41 342 ;; only look at pairings for the shorter note. This can
d@41 343 ;; have odd side effects, but means we never
d@41 344 ;; under-represent an overlap (I think)
d@41 345 (when (or (not (aref overlaps shorter))
d@41 346 (>= overlap (aref overlaps shorter)))
d@41 347 (setf (aref overlaps shorter) overlap)
d@41 348 (when (and (= shorter i)
d@41 349 (= overlap 1))
d@41 350 ;; Maximum value - we can stop
d@41 351 (return)))))))
d@41 352 (let ((total 0) (overs 0))
d@41 353 (loop for i from 0 to (1- (length events-bag))
d@41 354 do (when (aref overlaps i)
d@41 355 (incf total)
d@41 356 (when (>= (aref overlaps i) 3/4)
d@41 357 (incf overs))))
d@41 358 (if (and (> total 0)
d@41 359 (>= (/ overs total)
d@41 360 1/4))
d@41 361 'T
d@47 362 'nil))))
d@47 363
d@47 364 (defgeneric inter-onset-intervals (composition &key rounding-divisor))
d@47 365 (defmethod inter-onset-intervals ((composition composition) &key (rounding-divisor 1/4))
d@47 366 ;; returns values - list inter-onset intervals in beats, modal i-o-i
d@47 367 ;; and i-o-is in seconds.
d@47 368 ;; ** Only makes sense for monodic music
d@47 369 ;; FIXME: Should this keep in objects or am I right to make numbers
d@47 370 ;; here?
d@47 371 ;; FIXME: Should I (do I) filter out 0s?
d@47 372 (let ((i-o-i-list) (i-o-i-secs-list) (prev)
d@47 373 (hits (make-array (/ 32 rounding-divisor))))
d@47 374 (loop for event being the elements of composition
d@47 375 do (progn
d@47 376 (when prev
d@47 377 (let* ((i-o-i-period (inter-onset-interval prev event))
d@47 378 (i-o-i (duration i-o-i-period))
d@47 379 (i-o-i-secs (amuse-utils:beats-to-seconds i-o-i-period composition)))
d@47 380 (when (= i-o-i-secs 0)
d@47 381 (format t "~D, ~D -- " (timepoint prev) (timepoint event)))
d@47 382 (push i-o-i i-o-i-list)
d@47 383 (push i-o-i-secs i-o-i-secs-list)
d@47 384 (when (< i-o-i 32)
d@47 385 ;; Not really interested in very long results for the
d@47 386 ;; modal value anyway.
d@47 387 (incf (aref hits (round i-o-i rounding-divisor))))))
d@47 388 (setf prev event)))
d@47 389 (let ((mode '(0 0)))
d@47 390 ;; we want the position of the highest mode
d@47 391 (loop for i downfrom (1- (length hits)) to 0
d@47 392 when (> (aref hits i) (car mode))
d@47 393 do (setf mode (list (aref hits i) i)))
d@47 394 (values (reverse i-o-i-list)
d@47 395 (* (cadr mode) rounding-divisor)
d@47 396 (reverse i-o-i-secs-list)))))
d@47 397
d@47 398 (defun pitch-interval-list (composition)
d@47 399 (let ((intervals)
d@47 400 (previous-event))
d@47 401 (sequence:dosequence (event composition (reverse intervals))
d@47 402 (when previous-event
d@47 403 (push (span (pitch- event previous-event))
d@47 404 intervals))
d@47 405 (setf previous-event event))))