annotate utils/harmony/chord-labelling.lisp @ 41:90abdf9adb60

monodising and some n-gram utilities darcs-hash:20070614140028-f76cc-9bdeba6db4097e425b1fee4f58a3327eeb486685.gz
author David Lewis <d.lewis@gold.ac.uk>
date Thu, 14 Jun 2007 15:00:28 +0100
parents 81b4228e26f5
children
rev   line source
d@33 1 (in-package #:amuse-harmony)
d@33 2
d@33 3 ;; This file contains functions for performing harmonic analysis and
d@33 4 ;; chord labelling. At the moment it's quite crude.
d@33 5 ;;
d@33 6 ;; Probability can be estimated based on a function that must take a
d@33 7 ;; window on the music (i.e. an anchored period and a composition (?
d@33 8 ;; or perhaps a 'constituent' in future?). The functionality below is
d@33 9 ;; a cut-down version of its predecessors and only models one pitch
d@33 10 ;; model, derived by combining dirichlet distributions on the local
d@33 11 ;; distribution of pitch-class durations in terms of
d@33 12 ;; chord-note:non-chord-note ratios and relative weighting of chord
d@33 13 ;; notes.
d@33 14 ;;
d@33 15 ;; * Chord objects contain details of chord types including the
d@33 16 ;; intervals of their constituents and any putative distributional
d@33 17 ;; information or note profiles or templates.
d@33 18 ;;
d@33 19 ;; * Chordset objects gather chord-types together for a given
d@33 20 ;; experiment. They have a slot for priors for historical reasons, but
d@33 21 ;; at the moment this is unused - I'm using other structures for this.
d@33 22 ;;
d@33 23 ;; * likelihoods are currently alists with a host of methods. (FIXME:
d@33 24 ;; this doesn't seem very clever)
d@33 25 ;;
d@33 26
d@33 27 ;; FIXME: this is in the wrong place
d@34 28 (defparameter *keys* (make-array 12 :initial-contents '(:c :c# :d :eb :e :f :f# :g :ab :a :bb :b)))
d@33 29
d@33 30 (defparameter *path-options*
d@33 31 ;; Each of these is a set of division-of-the-bar options for each
d@33 32 ;; metrical type.
d@33 33 ;;
d@33 34 ;; FIXME: behaviour if the time-signature numerator is absent from
d@33 35 ;; the alist is undefined.
d@33 36 '((4 (1 1 1 1) (1 1 2) (1 2 1) (1 3)
d@33 37 (2 1 1) (2 2) (3 1) (4))
d@33 38 ;; (4 (1 1 1 1) (1 1 2) (2 1 1) (2 2) (4))
d@33 39 (2 (1 1) (2))
d@33 40 (3 (1 1 1) (1 2) (2 1) (3))
d@33 41 (6 (3 3) (6))
d@33 42 (5 (1 1 1 1 1) (1 1 1 2) (1 1 2 1) (1 1 3)
d@33 43 (1 2 1 1) (1 2 2) (1 3 1) (1 4)
d@33 44 (2 1 1 1) (2 1 2) (2 2 1) (2 3)
d@33 45 (3 1 1) (3 2) (4 1) (5))
d@33 46 (9 (3 3 3) (3 6) (6 3) (9))
d@33 47 (12 (3 3 3 3) (3 3 6) (3 6 3) (3 9)
d@33 48 (6 3 3) (6 6) (9 3) (12))))
d@33 49
d@33 50 #+nil
d@33 51 (defparameter *default-models* '(:constant-prior :gamma))
d@33 52 #+nil
d@33 53 (defparameter *default-models* '(:scaled-prior :gamma :naive-bass))
d@33 54 ;; #+nil
d@33 55 (defparameter *default-models* '(:scaled-prior :gamma))
d@33 56 #+nil
d@33 57 (defparameter *default-models* '(:scaled-prior :gamma :metrical-prior))
d@33 58
d@33 59 ;;; ACCESSORS
d@33 60 ;; Nearly empty now. And not much point in what's left
d@33 61 (defgeneric normalised-distribution (chord &optional total))
d@33 62 (defmethod normalised-distribution ((chord chord) &optional (total 1))
d@33 63 ;; normalised distributions will be reused, so it makes sense to
d@33 64 ;; store them.
d@33 65 ;; FIXME: Are these ever going to be useful again?
d@33 66 (unless (assoc total (slot-value chord 'normalised-distribution))
d@33 67 (setf (slot-value chord 'normalised-distribution)
d@33 68 (acons total (normalise-vector (slot-value chord 'distribution) total)
d@33 69 (slot-value chord 'normalised-distribution))))
d@33 70 (cdr (assoc total (slot-value chord 'normalised-distribution))))
d@33 71
d@33 72 ;;; Object definitions
d@33 73
d@33 74 #+nil
d@33 75 (defparameter *major-ratios* (mapcar (lambda (x) (/ x 217))
d@33 76 '(180 1 1 1 20 1 1 8 1 1 1 1)))
d@33 77 #+nil
d@33 78 (defparameter *major-ratios* (mapcar (lambda (x) (/ x 20))
d@33 79 '(5 1 2 1 3 1 1 2 1 1 1 1)))
d@33 80 (defparameter *major-ratios* #(0.72 0.02 0.02 0.02 0.08 0.02 0.02 0.02 0.02 0.02 0.02 0.02))
d@33 81 #+nil
d@33 82 (defparameter *minor-ratios* (mapcar (lambda (x) (/ x 302))
d@33 83 '(280 1 1 4 1 1 1 9 1 1 1 1)))
d@33 84 (defparameter *minor-ratios* #(0.78 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02))
d@33 85 #+nil
d@33 86 (defparameter *minor-ratios* (mapcar (lambda (x) (/ x 20))
d@33 87 '(7 1 2 2 1 1 1 1 1 1 1 1)))
d@33 88 #+nil
d@33 89 (defparameter *sus-ratios* (mapcar (lambda (x) (/ x 21))
d@33 90 '(10 1 1 1 1 1 1 1 1 1 1 1)))
d@33 91 (defparameter *sus-ratios* #(0.78 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02 0.02))
d@33 92
d@33 93 (defparameter *dim-ratios* (copy-seq *sus-ratios*))
d@33 94 (defparameter *aug-ratios* (copy-seq *sus-ratios*))
d@33 95
d@33 96 ;; Chords
d@33 97 (defparameter *major-chord*
d@33 98 (make-instance 'chord
d@33 99 :label :major
d@33 100 :notes '(0 4 7)
d@33 101 :bass-likelihoods (make-array 12
d@33 102 :initial-contents *major-ratios*)
d@33 103 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
d@33 104 :distribution (make-array 12
d@33 105 :initial-contents '(6 1 2 1 5 2 1 5 1 2 2 2))))
d@33 106
d@33 107 (defparameter *minor-chord*
d@33 108 (make-instance 'chord :label :minor :notes '(0 3 7)
d@33 109 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
d@33 110 :bass-likelihoods (make-array 12
d@33 111 :initial-contents *minor-ratios*)
d@33 112 :distribution (make-array 12
d@33 113 :initial-contents '(6 1 2 5 1 2 1 5 2 1 2 1))))
d@33 114
d@33 115 (defparameter *diminished-chord*
d@33 116 (make-instance 'chord :label :dim :notes '(0 3 6)
d@33 117 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
d@33 118 :bass-likelihoods (make-array 12
d@33 119 :initial-contents *dim-ratios*)
d@33 120 :distribution (make-array 12
d@33 121 :initial-contents '(6 1 1 5 1 1 5 1 1 4 1 1))))
d@33 122
d@33 123 (defparameter *diminished-chord-short*
d@33 124 (make-instance 'chord :label :dim :notes '(0 3 6 9)
d@33 125 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
d@33 126 :distribution (make-array 3
d@33 127 :initial-contents '(6 1 1))))
d@33 128
d@33 129 (defparameter *augmented-chord*
d@33 130 (make-instance 'chord :label :aug :notes '(0 4 8) :min-distribution #(35 25 20 20)
d@33 131 :bass-likelihoods (make-array 12
d@33 132 :initial-contents *aug-ratios*)
d@33 133 :distribution (make-array 12
d@33 134 :initial-contents '(6 1 1 1 5 1 1 1 5 1 1 1))))
d@33 135
d@33 136 (defparameter *augmented-chord-short*
d@33 137 (make-instance 'chord :label :aug :notes '(0 4 8)
d@33 138 :distribution (make-array 4
d@33 139 :initial-contents '(6 1 1 1))))
d@33 140
d@33 141 (defparameter *suspended4th-chord*
d@33 142 (make-instance 'chord :label :sus4 :notes '(0 5 7)
d@33 143 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
d@33 144 :bass-likelihoods (make-array 12
d@33 145 :initial-contents *sus-ratios*)
d@33 146 :distribution (make-array 12
d@33 147 :initial-contents '(6 1 2 3 3 5 1 5 1 1 2 2))))
d@33 148
d@33 149 (defparameter *suspended9th-chord*
d@33 150 (make-instance 'chord :label :sus9 :notes '(0 2 7)
d@33 151 :min-distribution (make-array 4 :initial-contents '(35 25 20 20))
d@33 152 :bass-likelihoods (make-array 12
d@33 153 :initial-contents *sus-ratios*)
d@33 154 :distribution (make-array 12
d@33 155 :initial-contents '(6 1 5 2 2 2 1 5 1 2 2 1))))
d@33 156
d@33 157 ;; CHORDSETS
d@33 158 (defparameter *full-set*
d@33 159 (make-instance 'chordset :chords (list *major-chord* *minor-chord*
d@33 160 *diminished-chord* *augmented-chord*
d@33 161 *suspended4th-chord* *suspended9th-chord*)))
d@33 162
d@33 163 (defparameter *full-set-variable-length*
d@33 164 (make-instance 'chordset :chords (list *major-chord* *minor-chord*
d@33 165 *diminished-chord-short* *augmented-chord-short*
d@33 166 *suspended4th-chord* *suspended9th-chord*)))
d@33 167
d@33 168 (defparameter *partial-set*
d@33 169 (make-instance 'chordset :chords (list *major-chord* *minor-chord*
d@33 170 *diminished-chord* *augmented-chord*)))
d@33 171
d@33 172 (defparameter *partial-set-variable-length*
d@33 173 (make-instance 'chordset
d@33 174 :chords (list *major-chord* *minor-chord*
d@33 175 *diminished-chord-short*
d@33 176 *augmented-chord-short*)))
d@33 177
d@33 178 (defparameter *minimal-set*
d@33 179 (make-instance 'chordset :chords (list *major-chord* *minor-chord*)))
d@33 180
d@33 181 #+nil
d@33 182 (defparameter *chord-proportions* ;; guess
d@33 183 (list (cons *major-chord* 17/30) (cons *minor-chord* 10/30)
d@33 184 (cons *diminished-chord* 1/60) (cons *augmented-chord* 1/60)
d@33 185 (cons *suspended4th-chord* 1/30) (cons *suspended9th-chord* 1/30)))
d@33 186
d@33 187 #+nil
d@33 188 (defparameter *chord-proportions* ;; another guess
d@33 189 (list (cons *major-chord* 1/3) (cons *minor-chord* 1/3)
d@33 190 (cons *diminished-chord* 1/30) (cons *augmented-chord* 1/60)
d@33 191 (cons *suspended4th-chord* 1/5) (cons *suspended9th-chord* 1/12)))
d@33 192 #+nil
d@33 193 (defparameter *chord-proportions* ;; flat
d@33 194 (list (cons *major-chord* 1/6) (cons *minor-chord* 1/6)
d@33 195 (cons *diminished-chord* 1/6) (cons *augmented-chord* 1/6)
d@33 196 (cons *suspended4th-chord* 1/6) (cons *suspended9th-chord* 1/6)))
d@33 197
d@33 198 (defparameter *chord-proportions*
d@33 199 ;; observed
d@33 200 ;; FIXME: This seriously impairs dim and aug. Do they ever get
d@33 201 ;; diagnosed now?
d@33 202 (list (cons *major-chord* 546/917) (cons *minor-chord* 312/917)
d@33 203 (cons *diminished-chord* 2/917) (cons *augmented-chord* 1/917)
d@33 204 (cons *suspended4th-chord* 44/917) (cons *suspended9th-chord* 12/917)))
d@33 205
d@33 206
d@33 207 ;; First steps to chord labelling
d@33 208 (defun get-chord-likelihoods-for-model (anchored-period music
d@33 209 &key (model :gamma)
d@33 210 (chordset *full-set*))
d@33 211 ;; Currently expects and returns an alist of (identifier
d@33 212 ;; . likelihood) (unnormalised, but can use normalise-likelihoods)
d@33 213 (ecase model
d@33 214 (:constant-prior
d@33 215 ;; results are divided by number of chords in chordset (times 12)
d@33 216 (constant-prior-likelihoods anchored-period
d@33 217 music
d@33 218 chordset))
d@33 219 (:scaled-prior
d@33 220 ;; results are divided by preset chord weightings (times 12)
d@33 221 (scaled-prior-likelihoods anchored-period
d@33 222 music
d@33 223 chordset))
d@33 224 (:naive-bass
d@33 225 (naive-bass-prior-likelihoods anchored-period
d@33 226 music
d@33 227 chordset))
d@33 228 (:metrical-prior
d@33 229 (metrical-prior-likelihoods anchored-period
d@33 230 music
d@33 231 chordset))
d@33 232 (:gamma
d@33 233 ;; dirichlet-based likelihood calculation
d@33 234 (3ple-gamma-likelihoods anchored-period
d@33 235 music
d@33 236 chordset))))
d@33 237
d@33 238 ;; LIKELIHOOD-CALCULATION FUNCTIONS
d@33 239 (defgeneric metrical-prior-likelihoods (anchored-period music chordset))
d@33 240 (defmethod metrical-prior-likelihoods ((anchored-period anchored-period)
d@33 241 music chordset)
d@33 242 (let* ((metrical-level (metrical-level-for-likelihood anchored-period music))
d@33 243 (p (if (= metrical-level 1)
d@33 244 0.51
d@33 245 0.07)))
d@33 246 (loop for chord in (chords chordset)
d@33 247 nconc (loop for i from 0 to 11
d@33 248 collect (cons (list i chord)
d@33 249 (/ p (* 12 (length (chords chordset)))))))))
d@33 250
d@33 251 (defgeneric constant-prior-likelihoods (anchored-period music chordset))
d@33 252 (defmethod constant-prior-likelihoods ((anchored-period anchored-period)
d@33 253 music chordset)
d@33 254 ;; returns a flat distribution totalling 1
d@33 255 (loop for chord in (chords chordset)
d@33 256 nconc (loop for i from 0 to 11
d@33 257 collect (cons (list i chord)
d@33 258 (/ 1 (* 12 (length
d@33 259 (chords chordset))))))))
d@33 260
d@33 261 (defgeneric scaled-prior-likelihoods (anchored-period music chordset &key prior-alist))
d@33 262 (defmethod scaled-prior-likelihoods ((anchored-period anchored-period) music chordset
d@33 263 &key (prior-alist *chord-proportions*))
d@33 264 ;; returns a distribution based on the relative likelihood of chord types
d@33 265 (loop for chord in (chords chordset)
d@33 266 nconc (loop for i from 0 to 11
d@33 267 collect (cons (list i chord)
d@33 268 (/ (cdr (assoc chord prior-alist))
d@33 269 12)))))
d@33 270
d@33 271 (defgeneric naive-bass-prior-likelihoods (anchored-period music chordset))
d@33 272 (defmethod naive-bass-prior-likelihoods ((anchored-period anchored-period) music chordset)
d@33 273 (let ((pc (bass-note anchored-period music)))
d@33 274 (loop for chord in (chords chordset)
d@33 275 nconc (loop for i from 0 to 11
d@33 276 collect (cons (list i chord)
d@33 277 (/ (aref (bass-likelihoods chord)
d@33 278 (mod (+ i pc) 12))
d@33 279 (length (chords chordset))))))))
d@33 280
d@33 281 (defgeneric 3ple-gamma-likelihoods (anchored-period music chordset))
d@33 282 (defmethod 3ple-gamma-likelihoods ((anchored-period anchored-period) music chordset)
d@33 283 ;; Ask Christophe about what this one does - this function just
d@33 284 ;; provides data to his dirichlet likelihood functions. Currently
d@33 285 ;; limited to triads, this has two distributions for relative
d@33 286 ;; strengths of chord notes and for the relation between chord and
d@33 287 ;; non-chord notes.
d@33 288 (let ((pitch-classes (normalised-pitch-class-distribution anchored-period music))
d@33 289 (metrical-level (metrical-level-for-likelihood anchored-period music))
d@33 290 (likelihoods))
d@33 291 (dolist (chord (chords chordset) likelihoods)
d@33 292 (let ((chord-likelihoods (subseq (min-distribution chord) 0 3))
d@33 293 (non-chord (aref (min-distribution chord) 3)))
d@33 294 (loop for offset from 0 to 11
d@33 295 do (setf likelihoods
d@33 296 (set-likelihood
d@33 297 likelihoods chord offset
d@33 298 (3ple-likelihood (chromatic-rotate pitch-classes (- offset))
d@33 299 chord-likelihoods
d@33 300 non-chord
d@33 301 (main-notes chord)
d@33 302 metrical-level 1
d@33 303 (get-alphas chord metrical-level :version :learned)
d@33 304 (get-betas chord metrical-level :version :learned)))))))))
d@33 305
d@33 306 #+nil
d@33 307 (defun get-alphas (chord metrical-level &key (version :map))
d@33 308 ;; MP values, map commented
d@33 309 (cond
d@33 310 ((or (eq *major-chord* chord)
d@33 311 (eq *minor-chord* chord))
d@33 312 (cond
d@33 313 ((< metrical-level 1)
d@33 314 (case version
d@33 315 (:map #(3.7812 2.4955 2.1525))
d@33 316 (:ml #(4.0398 2.6624 2.2942))
d@33 317 (:learned #(2.0475 1.365 1.1374999))))
d@33 318 (t
d@33 319 (case version
d@33 320 (:map #(3.6626 1.5234 2.3395))
d@33 321 (:ml #(3.9119 1.6193 2.4955))
d@33 322 (:learned #(2.0475 1.365 1.1374999))))))
d@33 323 (t
d@33 324 (case version
d@33 325 (:map #(3.5110 2.0252 1.2963))
d@33 326 (:ml #(4.0822 2.3459 1.4874))
d@33 327 (:learned #(2.0475 1.365 1.1374999))))))
d@33 328
d@33 329 ;; New, corrected ground truth
d@33 330 (defun get-alphas (chord metrical-level &key (version :map))
d@33 331 ;; MP values, map commented
d@33 332 (cond
d@33 333 ((or (eq *major-chord* chord)
d@33 334 (eq *minor-chord* chord))
d@33 335 (cond
d@33 336 ((< metrical-level 1)
d@33 337 (case version
d@33 338 (:map #(3.7397 2.4923 2.0187))
d@33 339 (:ml #(3.9434 2.6253 2.1239))
d@33 340 (:learned #(2.0475 1.365 1.1374999))))
d@33 341 (t
d@33 342 (case version
d@33 343 (:map #(3.2620 1.3882 2.2542))
d@33 344 (:ml #(3.5200 1.4889 2.4293))
d@33 345 (:learned #(2.0475 1.365 1.1374999))))))
d@33 346 (t
d@33 347 (case version
d@33 348 (:map #(3.1963 1.8187 1.3340))
d@33 349 (:ml #(3.6371 2.0621 1.2799))
d@33 350 (:learned #(2.0475 1.365 1.1374999))))))
d@33 351
d@33 352 #+nil
d@33 353 (defun get-betas (chord metrical-level &key (version :map))
d@33 354 (cond
d@33 355 ((eq version :learned)
d@33 356 (cond
d@33 357 ((> metrical-level 1/2)
d@33 358 #(0.97 12))
d@33 359 ((= metrical-level 1/2)
d@33 360 #(0.97 6))
d@33 361 (t #(0.97 4))))
d@33 362 ((or (eq *major-chord* chord)
d@33 363 (eq *minor-chord* chord))
d@33 364 (if (< metrical-level 1)
d@33 365 (if (eq version :map)
d@33 366 #(0.6987 3.1724)
d@33 367 #(0.7164 3.2640))
d@33 368 (if (eq version :map)
d@33 369 #(1.3677 5.9215)
d@33 370 #(1.4454 6.2843))))
d@33 371 (t
d@33 372 (if (eq version :map)
d@33 373 #(0.9358 5.2212)
d@33 374 #(1.0431 5.8530)))))
d@33 375
d@33 376 ;; With new, corrected ground truth
d@33 377 (defun get-betas (chord metrical-level &key (version :map))
d@33 378 (cond
d@33 379 ((eq version :learned)
d@33 380 (cond
d@33 381 ((> metrical-level 1/2)
d@33 382 #(0.97 12))
d@33 383 ((= metrical-level 1/2)
d@33 384 #(0.97 6))
d@33 385 (t #(0.97 4))))
d@33 386 ((or (eq *major-chord* chord)
d@33 387 (eq *minor-chord* chord))
d@33 388 (if (< metrical-level 1)
d@33 389 (if (eq version :map)
d@33 390 #(0.7041 3.3448)
d@33 391 #(0.7190 3.4260))
d@33 392 (if (eq version :map)
d@33 393 #(1.3838 6.4581)
d@33 394 #(1.4872 6.9785))))
d@33 395 (t
d@33 396 (if (eq version :map)
d@33 397 #(0.9558 5.0847)
d@33 398 #(1.0551 5.6740)))))
d@33 399
d@33 400
d@33 401 (defun chromatic-rotate (vector offset)
d@33 402 ;; transpose an n-member (chromatic) vector by an integral number of
d@33 403 ;; steps (semitones)
d@33 404 (let* ((size (length vector))
d@33 405 (result (make-array size)))
d@33 406 (dotimes (i size result)
d@33 407 (setf (aref result i) (aref vector (mod (- i offset) size))))))
d@33 408
d@33 409 (defgeneric metrical-level-for-likelihood (anchored-period music))
d@33 410 (defmethod metrical-level-for-likelihood (anchored-period (music composition))
d@33 411 ;; metrical level is a function of time signature and window size
d@33 412 ;; and is used to modify the gamma function.
d@33 413 (let ((time-sigs (get-applicable-time-signatures anchored-period music)))
d@33 414 (cond
d@33 415 ((= (length time-sigs) 1)
d@33 416 (/ (duration anchored-period)
d@33 417 (crotchets-in-a-bar (first time-sigs))))
d@33 418 ((null time-sigs)
d@33 419 ;; If, for some reason, we have no time-signature, midi specs
d@33 420 ;; say assume 4/4.
d@33 421 (/ (duration anchored-period) 4))
d@33 422 (t
d@33 423 (loop for sig in time-sigs
d@33 424 sum (/ (duration (period-intersection sig
d@33 425 anchored-period))
d@33 426 (crotchets-in-a-bar sig)))))))
d@33 427
d@33 428 ;;;;;;;;;;;;;;;;;;;;;;;;
d@33 429 ;;
d@33 430 ;; Hypothesis comparison / level navigation
d@33 431 ;;
d@33 432
d@33 433 (defun chord-labels (anchored-period music
d@33 434 &key (chordset *full-set*)
d@33 435 (models *default-models*))
d@33 436 (let ((harmonic-analysis (best-level anchored-period music :chordset chordset :models models))
d@33 437 (best-likelihood) (chord-labels))
d@33 438 (do ((path (first harmonic-analysis) (cdr path))
d@33 439 (likelihoods (second harmonic-analysis) (cdr likelihoods)))
d@33 440 ((null path) (reverse chord-labels))
d@33 441 (dolist (likelihood (car likelihoods))
d@33 442 (when (or (null best-likelihood)
d@33 443 (> (likelihood-likelihood likelihood)
d@33 444 (likelihood-likelihood best-likelihood)))
d@33 445 (setf best-likelihood likelihood)))
d@33 446 (push (cons (first path) (likelihood-chord best-likelihood)) chord-labels)
d@33 447 (setf best-likelihood nil))))
d@33 448
d@33 449 (defun best-level (anchored-period music
d@33 450 &key (chordset *full-set*)
d@33 451 (models *default-models*))
d@33 452 ;; Takes a period for the largest time-unit being considered and
d@33 453 ;; returns the highest probability subdivision, its likelihood
d@33 454 ;; values (and the probability of that subdivision, but that's a bit
d@33 455 ;; of a coincidence and may want not to happen)
d@33 456 (best-level-hypothesis (make-metrical-divisions anchored-period music)
d@33 457 music :chordset chordset :models models))
d@33 458
d@33 459 (defgeneric make-metrical-divisions (anchored-period music))
d@33 460 (defmethod make-metrical-divisions ((anchored-period anchored-period)
d@33 461 (music composition))
d@33 462 ;; Prepares a set of divisions of the period based on time-sig and a
d@33 463 ;; pre-set list of options for each possible time-sig numerator.
d@33 464 (let ((time-sigs (get-applicable-time-signatures anchored-period music)))
d@33 465 (if
d@33 466 (< (length time-sigs) 2)
d@33 467 (let ((candidates))
d@33 468 ;; get an appropriate set of divisions. Not sure this is right
d@33 469 ;; - it relies on bar position being irrelevant. Is this true?
d@33 470 ;; This isn't really clear from this code, but if there are no
d@33 471 ;; time-signatures, make-divisions-with-timesigs has a test for
d@33 472 ;; it and will pretend it's 4/4.
d@33 473 (dolist (divisions (make-divisions-with-time-signature anchored-period (car time-sigs))
d@33 474 candidates)
d@33 475 (do ((time (onset anchored-period) (cut-off (car candidate-set)))
d@33 476 (divisions divisions (cdr divisions))
d@33 477 (candidate-set))
d@33 478 ((null divisions) (push (reverse candidate-set) candidates))
d@33 479 (push (make-anchored-period (timepoint time) (first divisions))
d@33 480 candidate-set))))
d@33 481 ;; otherwise, there are lots. Run this function once for each
d@33 482 ;; time-signature.
d@33 483 (loop for time-sig in time-sigs
d@33 484 nconc (make-metrical-divisions (period-intersection anchored-period time-sig)
d@33 485 music)))))
d@33 486
d@33 487 (defgeneric make-divisions-with-time-signature (period time-signature))
d@33 488 (defmethod make-divisions-with-time-signature ((period period-designator)
d@33 489 (time-signature basic-time-signature))
d@33 490 (let* ((numerator (time-signature-numerator time-signature))
d@33 491 (denominator (time-signature-denominator time-signature))
d@33 492 (path-options (cdr (assoc numerator *path-options*))))
d@33 493 (loop for divisions in path-options
d@33 494 collect (period-fill period divisions denominator))))
d@33 495
d@33 496 (defmethod make-divisions-with-time-signature ((period period-designator)
d@33 497 time-signature)
d@33 498 ;; not a known time-signature type. Assume 4/4
d@33 499 (let ((path-options (cdr (assoc 4 *path-options*))))
d@33 500 (loop for divisions in path-options
d@33 501 collect (period-fill period divisions 4))))
d@33 502
d@33 503 (defun period-fill (period path-options denominator)
d@33 504 ;; take a division of the ?bar and then repeat it until the period
d@33 505 ;; is filled.
d@33 506 ;;
d@33 507 ;; Perhaps this and surrounding function need to make more use of
d@33 508 ;; time interface?
d@33 509 (let ((duration-list)
d@33 510 ;; Multiply path-options by unit of meter.
d@33 511 (path-options (map 'list
d@33 512 #'(lambda (x) (* x (/ 4 denominator)))
d@33 513 path-options)))
d@33 514 (do* ((circular-path path-options (or (cdr circular-path)
d@33 515 path-options))
d@33 516 (current-duration (car circular-path) (car circular-path))
d@33 517 (prev-remaining (duration period) remaining)
d@33 518 (remaining (- (duration period) current-duration) (- remaining current-duration)))
d@33 519 ((<= remaining 0) (reverse (cons prev-remaining duration-list)))
d@33 520 (push current-duration duration-list))))
d@33 521
d@33 522 (defun best-level-hypothesis (division-hypotheses music
d@33 523 &key (chordset *full-set*)
d@33 524 (models *default-models*))
d@33 525 ;; Rather messy wrapper for level-hypothesis-likelihoods. Should
d@33 526 ;; probably make this a structure or something, but use looks like
d@33 527 ;; being quite limited. might revisit.
d@33 528 (first (sort (level-hypothesis-likelihoods division-hypotheses
d@33 529 music
d@33 530 :chordset chordset
d@33 531 :models models)
d@33 532 #'> :key #'third)))
d@33 533
d@33 534 (defun level-hypothesis-likelihoods (division-hypotheses music
d@33 535 &key (chordset *full-set*)
d@33 536 (models *default-models*))
d@33 537 ;; This function takes the candidate windows being considered (as
d@33 538 ;; lists of anchored periods) and, for each, works out likelihoods
d@33 539 ;; and the most probable hypothesis. This should come from taking
d@33 540 ;; the likelihoods and dividing by the product of the internal sums
d@33 541 ;; (don't ask!)
d@33 542 (let ((hypothesis-likelihoods))
d@33 543 (dolist (hypothesis division-hypotheses hypothesis-likelihoods)
d@33 544 (let ((likelihoods (map 'list
d@33 545 #'(lambda (x)
d@33 546 (get-chord-likelihoods x music models chordset))
d@33 547 hypothesis)))
d@33 548 (push (list hypothesis likelihoods (combined-likelihoods-sum likelihoods))
d@33 549 hypothesis-likelihoods)))))
d@33 550
d@33 551 (defun get-chord-likelihoods (anchored-period music models chordset)
d@33 552 (let ((model-likelihoods
d@33 553 (loop for model in models
d@33 554 collect (get-chord-likelihoods-for-model anchored-period
d@33 555 music
d@33 556 :model model
d@33 557 :chordset chordset))))
d@33 558 (combine-multimodel-likelihoods model-likelihoods)))
d@33 559
d@33 560 (defun combine-multimodel-likelihoods (likelihoods-list)
d@33 561 (cond
d@33 562 ((= (length likelihoods-list) 1)
d@33 563 (car likelihoods-list))
d@33 564 (t
d@33 565 (let ((combined-likelihoods))
d@33 566 (dolist (reference-likelihood (car likelihoods-list) combined-likelihoods)
d@33 567 (setf combined-likelihoods
d@33 568 (set-likelihood combined-likelihoods
d@33 569 (likelihood-chordtype reference-likelihood)
d@33 570 (likelihood-pitch-class reference-likelihood)
d@33 571 (apply #'* (loop for model-likelihoods in likelihoods-list
d@33 572 collect (likelihood-likelihood
d@33 573 (assoc (car reference-likelihood)
d@33 574 model-likelihoods
d@33 575 :test #'equal)))))))))))
d@33 576
d@33 577 ;;;;;;;;;;;;;;;;;;;;;;;;;
d@33 578 ;;
d@33 579 ;; Likelihood (structure) manipulation and access methods
d@33 580 ;;
d@41 581 ;; FIXME: Explain this?? Is it ((pc :type) . p(chord))?
d@33 582
d@33 583 (defgeneric set-likelihood (likelihoods offset chord likelihood))
d@33 584 (defmethod set-likelihood ((likelihoods list) chord offset likelihood)
d@33 585 (acons (list offset chord) likelihood likelihoods))
d@33 586
d@33 587 (defgeneric get-likelihood (likelihoods offset chord))
d@33 588 (defmethod get-likelihood ((likelihoods list) offset chord)
d@33 589 (assoc (list offset chord) likelihoods :test #'equal))
d@33 590
d@33 591 (defgeneric best-n-likelihoods (n likelihoods))
d@33 592 (defmethod best-n-likelihoods (n (likelihoods list))
d@33 593 (let ((ranked (ordered-likelihoods likelihoods)))
d@33 594 (subseq ranked 0 n)))
d@33 595
d@33 596 (defgeneric ordered-likelihoods (likelihoods))
d@33 597 (defmethod ordered-likelihoods ((likelihoods list))
d@33 598 (sort (copy-seq likelihoods) #'> :key #'cdr))
d@33 599
d@33 600 (defgeneric pretty-display-likelihoods (likelihoods))
d@33 601 (defmethod pretty-display-likelihoods ((likelihoods list))
d@33 602 (dolist (p likelihoods)
d@33 603 (format *standard-output* "~%~A~C~A~C~A"
d@33 604 (likelihood-key p) #\Tab
d@33 605 (chord-label (likelihood-chordtype p)) #\Tab
d@33 606 (likelihood-likelihood p))))
d@33 607
d@33 608 (defgeneric likelihood-key (likelihood))
d@33 609 (defmethod likelihood-key ((likelihood list))
d@33 610 (aref *keys* (first (first likelihood))))
d@33 611
d@33 612 (defgeneric likelihood-pitch-class (likelihood))
d@33 613 (defmethod likelihood-pitch-class ((likelihood list))
d@33 614 (first (first likelihood)))
d@33 615
d@33 616 (defgeneric likelihood-chordtype (likelihood))
d@33 617 (defmethod likelihood-chordtype ((likelihood list))
d@33 618 (second (first likelihood)))
d@33 619
d@33 620 (defgeneric likelihood-chord (likelihood))
d@33 621 (defmethod likelihood-chord ((likelihood list))
d@33 622 (first likelihood))
d@33 623
d@33 624 (defgeneric likelihood-likelihood (likelihood))
d@33 625 (defmethod likelihood-likelihood ((likelihood list))
d@33 626 (cdr likelihood))
d@33 627
d@33 628 (defgeneric likelihoods-sum (likelihoods))
d@33 629 (defmethod likelihoods-sum ((likelihoods list))
d@33 630 (loop for likelihood in likelihoods
d@33 631 sum (likelihood-likelihood likelihood)))
d@33 632
d@33 633 (defgeneric combined-likelihoods-sum (combined-likelihoods))
d@33 634 (defmethod combined-likelihoods-sum ((combined-likelihoods list))
d@33 635 ;; Will be needed for hypothesis comparison - sums the likelihoods
d@33 636 ;; for all chords within a window for multiple likelihood
d@33 637 ;; calculations
d@33 638 (apply #'* (map 'list #'(lambda (window)
d@33 639 (loop for likelihood in window
d@33 640 sum (likelihood-likelihood likelihood)))
d@33 641 combined-likelihoods)))
d@33 642
d@33 643 (defgeneric normalise-likelihoods (likelihoods))
d@33 644 (defmethod normalise-likelihoods ((likelihoods list))
d@33 645 (let ((p-sum (sum-likelihoods likelihoods)))
d@33 646 (if (= p-sum 1)
d@33 647 likelihoods
d@33 648 (scale-likelihoods likelihoods (/ 1 p-sum)))))
d@33 649
d@33 650 (defgeneric scale-likelihoods (likelihoods scale-factor))
d@33 651 (defmethod scale-likelihoods ((likelihoods list) (scale-factor number))
d@33 652 (map 'list #'(lambda (x)
d@33 653 (cons (first x)
d@33 654 (* (cdr x) scale-factor)))
d@33 655 likelihoods))
d@33 656
d@33 657 (defgeneric sum-likelihoods (likelihoods))
d@33 658 (defmethod sum-likelihoods ((likelihoods list))
d@33 659 (apply #'+ (map 'list #'cdr likelihoods)))
d@33 660
d@33 661 ;; Probably useless vestigial stuff from here
d@33 662
d@33 663 (defun vector-list-apply (predicate vector-list &optional other-args)
d@33 664 (let ((result-list))
d@33 665 (dolist (vector vector-list (reverse result-list))
d@33 666 (push (make-array (array-dimensions vector)) result-list)
d@33 667 (loop for i from 0 to (1- (length vector))
d@33 668 do (setf (aref (first result-list) i)
d@33 669 (apply predicate (cons (aref vector i) other-args)))))))
d@33 670
d@33 671 (defun vector-sum (vector)
d@33 672 (loop for i from 0 to (1- (length vector))
d@33 673 sum (aref vector i)))
d@33 674
d@33 675 (defun make-flat-result (chordset)
d@33 676 (map 'list #'(lambda (x)
d@33 677 (make-array (length (distribution x))
d@33 678 :initial-element 0))
d@33 679 (chords chordset)))
d@33 680
d@33 681 (defun key-name (pitch-class)
d@33 682 (if pitch-class
d@33 683 (aref #("C" "C#" "D" "Eb" "E" "F" "F#" "G" "G#" "A" "Bb" "B") (mod pitch-class 12))
d@33 684 nil))