annotate utils/utils.lisp @ 37:9aeb5bff013a

Added levenshtein distance with cost functions darcs-hash:20070516124658-f76cc-8b96080b5c4018f2077650697bec4d1213917505.gz
author David Lewis <d.lewis@gold.ac.uk>
date Wed, 16 May 2007 13:46:58 +0100
parents ad321ce17e3e
children 262aa7a3d500
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@36 118 (do ((i 0 (1+ i)))
d@36 119 ((= i n) results-array)
d@36 120 (setf equation-t (- (* n (loop for j from 0 to (1- n)
d@36 121 sum (* (aref vector1 (mod (+ i j) n))
d@36 122 (aref vector2 j))))
d@36 123 equation-tr)
d@36 124 (aref results-array i) (/ equation-t equation-b)))))
d@36 125
d@36 126
d@36 127 (defparameter *krumhansl-schmuckler-major-key*
d@36 128 (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 129
d@36 130 (defparameter *krumhansl-schmuckler-minor-key*
d@36 131 (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 132
d@36 133 (defun krumhansl-key-finder (anchored-period composition
d@36 134 &key (major *krumhansl-schmuckler-major-key*)
d@36 135 (minor *krumhansl-schmuckler-minor-key*))
d@36 136 (let* ((key) (best-score -1)
d@36 137 (pitches (pitch-class-distribution anchored-period composition))
d@36 138 (majors (vector-correlation pitches major))
d@36 139 (minors (vector-correlation pitches minor)))
d@36 140 (loop for i from 0 to 11
d@36 141 do (when (> (aref majors i) best-score)
d@36 142 (setf key (list i :major)
d@36 143 best-score (aref majors i))))
d@36 144 (loop for i from 0 to 11
d@36 145 do (when (> (aref minors i) best-score)
d@36 146 (setf key (list i :minor)
d@36 147 best-score (aref minors i))))
d@36 148 key))
d@37 149
d@37 150 (defun levenshtein-distance (s1 s2 &key (insertion-cost 1)
d@37 151 (insertion-function) (deletion-cost 1)
d@37 152 (deletion-function) (substitution-cost 1)
d@37 153 (substitution-test #'equal) (substitution-function))
d@37 154 ;; This is an implementation of the Levenshtein distance measure
d@37 155 ;; based on the cliki asdf package, itself based on the wikipedia
d@37 156 ;; scheme example of the same algorithm. This version is generalised
d@37 157 ;; such that operations costs may take constant or calculated
d@37 158 ;; values. If insertion-function, deletion-function or
d@37 159 ;; substitution-test are specified, the applicable cost values are
d@37 160 ;; ignored and the function output is used instead.
d@37 161 (let* ((width (1+ (length s1)))
d@37 162 (height (1+ (length s2)))
d@37 163 (d (make-array (list height width))))
d@37 164 (dotimes (x width)
d@37 165 (setf (aref d 0 x) (* x deletion-cost)))
d@37 166 (dotimes (y height)
d@37 167 (setf (aref d y 0) (* y insertion-cost)))
d@37 168 (dotimes (x (length s1))
d@37 169 (dotimes (y (length s2))
d@37 170 (setf (aref d (1+ y) (1+ x))
d@37 171 (min (+ (if insertion-function
d@37 172 (apply insertion-function (elt s1 x))
d@37 173 insertion-cost)
d@37 174 (aref d y (1+ x)))
d@37 175 (+ (if deletion-function
d@37 176 (apply deletion-function (elt s2 y))
d@37 177 deletion-cost)
d@37 178 (aref d (1+ y) x))
d@37 179 (+ (aref d y x)
d@37 180 (if substitution-function
d@37 181 (apply substitution-function (list (elt s1 x) (elt s2 y)))
d@37 182 (if (apply substitution-test (list (elt s1 x) (elt s2 y)))
d@37 183 0
d@37 184 substitution-cost)))))))
d@37 185 (aref d (1- height) (1- width))))