annotate utils/utils.lisp @ 330:2fbff655ba47 tip

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