Mercurial > hg > amuse
changeset 33:d1010755f507
Large upload of local changes. Many additions, such as harmony and piece-level objects
darcs-hash:20070413100909-f76cc-a8aa8dfc07f438dc0c1a7c45cee7ace2ecc1e6a5.gz
author | David Lewis <d.lewis@gold.ac.uk> |
---|---|
date | Fri, 13 Apr 2007 11:09:09 +0100 |
parents | 5e705b6f94b6 |
children | 81b4228e26f5 |
files | amuse.asd base/classes.lisp base/constructors.lisp base/extended-sequence.lisp base/generics.lisp base/methods.lisp base/package.lisp implementations/midi/classes.lisp implementations/midi/package.lisp utils/harmony/chord-labelling.lisp utils/harmony/classes.lisp utils/harmony/evaluation.lisp utils/harmony/gamma.lisp utils/harmony/methods.lisp utils/harmony/package.lisp utils/midi-output.lisp utils/package.lisp utils/utils.lisp |
diffstat | 18 files changed, 2309 insertions(+), 28 deletions(-) [+] |
line wrap: on
line diff
--- a/amuse.asd Mon Dec 18 13:23:31 2006 +0000 +++ b/amuse.asd Fri Apr 13 11:09:09 2007 +0100 @@ -2,25 +2,35 @@ :name "amuse" :description "" :serial t + :depends-on ("midi") :components - ((:module utils + ((:module base + :components + ((:file "package") + (:file "conditions" :depends-on ("package")) + (:file "extended-sequence" :depends-on ("package")) + (:file "classes" :depends-on ("package")) + (:file "constructors" :depends-on ("package")) + (:file "generics" :depends-on ("package")) + (:file "methods" :depends-on ("package")))) + (:module utils :components ((:file "package") - (:file "utils"))) - (:module base - :components - ((:file "package") - (:file "conditions") - (:file "classes") - (:file "constructors") - (:file "generics") - (:file "methods"))) + (:file "utils" :depends-on ("package")) + (:file "midi-output" :depends-on ("package" "utils")) + (:module harmony + :depends-on ("utils") + :components + ((:file "package") + (:file "classes" :depends-on ("package")) + (:file "gamma" :depends-on ("package")) + (:file "chord-labelling" :depends-on ("gamma" "package" "classes")))))) (:module implementations :components ((:module midi :components ((:file "package") - (:file "classes") - (:file "constructors") - (:file "methods"))))))) + (:file "classes" :depends-on ("package")) + (:file "constructors" :depends-on ("package" "classes")) + (:file "methods" :depends-on ("package" "classes"))))))))
--- a/base/classes.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/base/classes.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -2,9 +2,13 @@ ;; collections of more than one event -(defclass constituent () ()) -(defclass composition (constituent) ()) -(defclass monody (constituent) ()) +(defclass constituent (anchored-period) ()) +(defclass time-ordered-constituent (constituent list-slot-sequence) + ;; this won't work if lisp implementation doesn't support extensible + ;; sequences. + ()) +(defclass composition (time-ordered-constituent) ()) +(defclass monody (time-ordered-constituent) ()) ;; types of information-specifiers
--- a/base/constructors.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/base/constructors.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -11,6 +11,8 @@ (defun make-floating-period (interval) (make-instance 'floating-period :interval interval)) + +;; Should this take a moment and/or a period too? (defun make-anchored-period (onset interval) (make-instance 'anchored-period :time onset
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/extended-sequence.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,74 @@ +(cl:in-package #:amuse) + +;; Defines a set of methods for a sequence one of whose slots is the +;; real sequence (in this case a list). This allows the allocation of +;; other slots for state information. +;; +;; Requires a lisp with extensible lists (Rhodes, User-extensible +;; Sequences, 2006/7??). Currently, that means SBCL >1.0 only. +;; +;; The code here draws heavily on Christophe's examples + +(defclass list-slot-sequence (sequence standard-object) + ((%data :accessor %list-slot-sequence-data + :initform nil))) + +(defmethod sequence:length ((o list-slot-sequence)) + (length (%list-slot-sequence-data o))) + +(defmethod sequence:elt ((o list-slot-sequence) index) + (elt (%list-slot-sequence-data o) index)) + +(defmethod (setf sequence:elt) (new-value (o list-slot-sequence) index) + (setf (elt (%list-slot-sequence-data o) index) new-value)) + +(defmethod sequence:make-sequence-like ((o list-slot-sequence) length + &key (initial-element nil iep) + (initial-contents nil icp)) + (let ((result (make-instance (class-of o)))) + (cond + ((and iep icp) + (error "Supplied both ~S and ~S to ~S" :initial-element :initial-contents 'make-sequence-like)) + (icp + (unless (= (length initial-contents) length) + (error "Length mismatch in ~S" 'make-sequence-like)) + (setf (%list-slot-sequence-data result) (coerce initial-contents 'list)) + result) + (t + (dotimes (i length result) + (push initial-element (%list-slot-sequence-data result))))))) + +(defmethod sequence:adjust-sequence ((o list-slot-sequence) length + &key initial-element + (initial-contents nil icp)) + (cond + ((= length 0) + (setf (%list-slot-sequence-data o) nil)) + (icp + (setf (%list-slot-sequence-data o) + (sequence:adjust-sequence (%list-slot-sequence-data o) + length + :initial-contents initial-contents))) + (t (setf (%list-slot-sequence-data o) + (sequence:adjust-sequence (%list-slot-sequence-data o) + length + :initial-element initial-element)))) + o) + +(defmethod sequence:make-simple-sequence-iterator + ((o list-slot-sequence) &rest args &key from-end start end) + (declare (ignore from-end start end)) + (apply #'sequence:make-simple-sequence-iterator + (%list-slot-sequence-data o) args)) +(defmethod sequence:iterator-step ((o list-slot-sequence) iterator from-end) + (sequence:iterator-step (%list-slot-sequence-data o) iterator from-end)) +(defmethod sequence:iterator-endp ((o list-slot-sequence) iterator limit from-end) + (sequence:iterator-endp (%list-slot-sequence-data o) iterator limit from-end)) +(defmethod sequence:iterator-element ((o list-slot-sequence) iterator) + (sequence:iterator-element (%list-slot-sequence-data o) iterator)) +(defmethod (setf sequence:iterator-element) (new-value (o list-slot-sequence) iterator) + (setf (sequence:iterator-element (%list-slot-sequence-data o) iterator) new-value)) +(defmethod sequence:iterator-index ((o list-slot-sequence) iterator) + (sequence:iterator-index (%list-slot-sequence-data o) iterator)) +(defmethod sequence:iterator-copy ((o list-slot-sequence) iterator) + (sequence:iterator-copy (%list-slot-sequence-data o) iterator)) \ No newline at end of file
--- a/base/generics.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/base/generics.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -4,6 +4,15 @@ (defgeneric get-composition (identifier)) +;;; Getting constituents from compositions +;; IS this the mechanism we want to use +(defgeneric time-signatures (composition)) +(defgeneric (setf time-signatures) (sequence composition)) +(defgeneric tempi (composition)) +(defgeneric (setf tempi) (sequence composition)) +(defgeneric key-signatures (composition)) +(defgeneric (setf key-signatures) (sequence composition)) + ;;; Simple Accessors ;; pitch-based @@ -44,6 +53,16 @@ (defgeneric beat-units (time-signature)) (defgeneric time-signature-denominator (time-signature) (:method (ts) (beat-units ts))) +(defgeneric tactus-duration (time-signature) + ;; basic, but should do? + (:method (ts) + (cond + ((and (not (= (beat-units-per-bar ts) 3)) + (= (rem (beat-units-per-bar ts) 3) 0)) + ;; compound time + (* (/ 4 (beat-units ts)) + 3)) + (t (/ 4 (beat-units ts)))))) (defgeneric key-signature-sharps (key-signature)) @@ -178,6 +197,15 @@ (or (before o1 o2) (meets o1 o2) (meets o2 o1) (before o2 o1)))) ;;; More time-based functions + +(defgeneric period= (object1 object2) + (:method (x y) nil)) + +(defgeneric find-overlapping (anchored-period sequence) + ;; Returns all members of a sequence of period signifiers that overlap + ;; with the supplied period + (:method (ap s) (remove-if #'(lambda (x) (amuse:disjoint ap x)) s))) + ;; Return the anchored-period representing the intersection of two ;; anchored-period-specifiers. (defgeneric period-intersection (anchored-period-specifier1 @@ -189,11 +217,13 @@ ;;; Time Signature -(defgeneric get-applicable-time-signatures (object1 object2)) +(defgeneric get-applicable-time-signatures (anchored-period composition) + (:method (ap c) (find-overlapping ap (time-signatures c)))) ;;; Tempo -(defgeneric get-applicable-tempi (object1 object2)) +(defgeneric get-applicable-tempi (anchored-period composition) + (:method (ap c) (find-overlapping ap (tempi c)))) ;;; Tonality (Key Signature / Mode)
--- a/base/methods.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/base/methods.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -15,9 +15,15 @@ (defmethod duration ((period-designator period)) (%period-interval period-designator)) +(defmethod (setf duration) ((value real) (period-designator period)) + (setf (%period-interval period-designator) value)) + (defmethod timepoint ((moment-designator moment)) (%moment-time moment-designator)) +(defmethod (setf timepoint) ((value real) (moment-designator moment)) + (setf (%moment-time moment-designator) value)) + (defmethod beat-units-per-bar ((time-signature basic-time-signature)) (%basic-time-signature-numerator time-signature)) @@ -197,6 +203,14 @@ ;; ...and +(defmethod period= ((object1 anchored-period) + (object2 anchored-period)) + (and (time= object1 object2) + (duration= object1 object2))) +(defmethod period= ((object1 floating-period) + (object2 floating-period)) + (duration= object1 object2)) + (defmethod period-intersection ((object1 anchored-period) (object2 anchored-period)) (cond @@ -212,7 +226,5 @@ (cut-off object2)) start)))) (make-anchored-period (timepoint start) duration))))) - - \ No newline at end of file
--- a/base/package.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/base/package.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -26,6 +26,9 @@ #:basic-key-signature #:tempo #:get-composition + #:time-signatures + #:tempi + #:key-signatures #:chromatic-pitch #:diatonic-pitch #:midi-pitch-number @@ -34,18 +37,19 @@ #:span #:duration #:timepoint - #:onset #:cut-off #:beat-units-per-bar #:time-signature-numerator #:beat-units #:time-signature-denominator + #:tactus-duration #:key-signature-sharps #:bpm #:microseconds-per-crotchet #:anchored-period #:floating-period #:moment + #:onset #:time+ #:time- #:time> @@ -84,6 +88,8 @@ #:ends #:subinterval #:disjoint + #:period= + #:find-overlapping #:period-intersection #:inter-onset-interval #:get-applicable-time-signatures
--- a/implementations/midi/classes.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/implementations/midi/classes.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -1,5 +1,16 @@ (cl:in-package #:amuse-midi) +(defclass midi-composition (amuse:composition) + ((time-signatures :initarg :time-signatures + :initform 'nil + :accessor %midi-time-signatures) + (tempi :initarg :tempi + :initform 'nil + :accessor %midi-tempi) + (misc-controllers :initarg :controllers + :initform 'nil + :accessor %midi-misc-controllers))) + (defclass midi-message () ;? ((channel :accessor %midi-message-channel :initarg :channel) (track :accessor %midi-message-track :initarg :track))) @@ -13,8 +24,10 @@ (defclass midi-percussive-event (percussive-event midi-message) ((velocity :initarg :velocity :accessor %midi-percussive-event-velocity) - (patch :initarg :patch) - (sound :initarg :sound))) + (patch :initarg :patch + :accessor %midi-percussive-event-patch) + (sound :initarg :sound + :accessor %midi-percussive-event-sound))) (defclass midi-key-signature (basic-key-signature) ;; Is mode ever used in real life? Is it ever accurately used in
--- a/implementations/midi/package.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/implementations/midi/package.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -1,6 +1,7 @@ (cl:defpackage #:amuse-midi - (:use #:common-lisp #:amuse) - (:export #:midi-pitched-event + (:use #:common-lisp #:amuse #:amuse-utils) + (:export #:midi-composition + #:midi-pitched-event #:midi-percussive-event #:midi-key-signature #:make-midi-pitched-event @@ -9,5 +10,10 @@ #:midi-channel #:midi-track #:midi-velocity - #:midi-patch)) + #:midi-patch + #:midi-drum-sound + #:time-signatures + #:tempi + #:microsecond-per-crotchet-to-bpm + ))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/harmony/chord-labelling.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,683 @@ +(in-package #:amuse-harmony) + +;; This file contains functions for performing harmonic analysis and +;; chord labelling. At the moment it's quite crude. +;; +;; Probability can be estimated based on a function that must take a +;; window on the music (i.e. an anchored period and a composition (? +;; or perhaps a 'constituent' in future?). The functionality below is +;; a cut-down version of its predecessors and only models one pitch +;; model, derived by combining dirichlet distributions on the local +;; distribution of pitch-class durations in terms of +;; chord-note:non-chord-note ratios and relative weighting of chord +;; notes. +;; +;; * Chord objects contain details of chord types including the +;; intervals of their constituents and any putative distributional +;; information or note profiles or templates. +;; +;; * Chordset objects gather chord-types together for a given +;; experiment. They have a slot for priors for historical reasons, but +;; at the moment this is unused - I'm using other structures for this. +;; +;; * likelihoods are currently alists with a host of methods. (FIXME: +;; this doesn't seem very clever) +;; + +;; FIXME: this is in the wrong place +(defparameter *keys* (make-array 12 :initial-contents '(:c :c# :d :eb :e :f :f# :g :g# :a :bb :b))) + +(defparameter *path-options* + ;; Each of these is a set of division-of-the-bar options for each + ;; metrical type. + ;; + ;; FIXME: behaviour if the time-signature numerator is absent from + ;; the alist is undefined. + '((4 (1 1 1 1) (1 1 2) (1 2 1) (1 3) + (2 1 1) (2 2) (3 1) (4)) + ;; (4 (1 1 1 1) (1 1 2) (2 1 1) (2 2) (4)) + (2 (1 1) (2)) + (3 (1 1 1) (1 2) (2 1) (3)) + (6 (3 3) (6)) + (5 (1 1 1 1 1) (1 1 1 2) (1 1 2 1) (1 1 3) + (1 2 1 1) (1 2 2) (1 3 1) (1 4) + (2 1 1 1) (2 1 2) (2 2 1) (2 3) + (3 1 1) (3 2) (4 1) (5)) + (9 (3 3 3) (3 6) (6 3) (9)) + (12 (3 3 3 3) (3 3 6) (3 6 3) (3 9) + (6 3 3) (6 6) (9 3) (12)))) + +#+nil +(defparameter *default-models* '(:constant-prior :gamma)) +#+nil +(defparameter *default-models* '(:scaled-prior :gamma :naive-bass)) +;; #+nil +(defparameter *default-models* '(:scaled-prior :gamma)) +#+nil +(defparameter *default-models* '(:scaled-prior :gamma :metrical-prior)) + +;;; ACCESSORS +;; Nearly empty now. And not much point in what's left +(defgeneric normalised-distribution (chord &optional total)) +(defmethod normalised-distribution ((chord chord) &optional (total 1)) + ;; normalised distributions will be reused, so it makes sense to + ;; store them. + ;; FIXME: Are these ever going to be useful again? + (unless (assoc total (slot-value chord 'normalised-distribution)) + (setf (slot-value chord 'normalised-distribution) + (acons total (normalise-vector (slot-value chord 'distribution) total) + (slot-value chord 'normalised-distribution)))) + (cdr (assoc total (slot-value chord 'normalised-distribution)))) + +;;; Object definitions + +#+nil +(defparameter *major-ratios* (mapcar (lambda (x) (/ x 217)) + '(180 1 1 1 20 1 1 8 1 1 1 1))) +#+nil +(defparameter *major-ratios* (mapcar (lambda (x) (/ x 20)) + '(5 1 2 1 3 1 1 2 1 1 1 1))) +(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)) +#+nil +(defparameter *minor-ratios* (mapcar (lambda (x) (/ x 302)) + '(280 1 1 4 1 1 1 9 1 1 1 1))) +(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)) +#+nil +(defparameter *minor-ratios* (mapcar (lambda (x) (/ x 20)) + '(7 1 2 2 1 1 1 1 1 1 1 1))) +#+nil +(defparameter *sus-ratios* (mapcar (lambda (x) (/ x 21)) + '(10 1 1 1 1 1 1 1 1 1 1 1))) +(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)) + +(defparameter *dim-ratios* (copy-seq *sus-ratios*)) +(defparameter *aug-ratios* (copy-seq *sus-ratios*)) + +;; Chords +(defparameter *major-chord* + (make-instance 'chord + :label :major + :notes '(0 4 7) + :bass-likelihoods (make-array 12 + :initial-contents *major-ratios*) + :min-distribution (make-array 4 :initial-contents '(35 25 20 20)) + :distribution (make-array 12 + :initial-contents '(6 1 2 1 5 2 1 5 1 2 2 2)))) + +(defparameter *minor-chord* + (make-instance 'chord :label :minor :notes '(0 3 7) + :min-distribution (make-array 4 :initial-contents '(35 25 20 20)) + :bass-likelihoods (make-array 12 + :initial-contents *minor-ratios*) + :distribution (make-array 12 + :initial-contents '(6 1 2 5 1 2 1 5 2 1 2 1)))) + +(defparameter *diminished-chord* + (make-instance 'chord :label :dim :notes '(0 3 6) + :min-distribution (make-array 4 :initial-contents '(35 25 20 20)) + :bass-likelihoods (make-array 12 + :initial-contents *dim-ratios*) + :distribution (make-array 12 + :initial-contents '(6 1 1 5 1 1 5 1 1 4 1 1)))) + +(defparameter *diminished-chord-short* + (make-instance 'chord :label :dim :notes '(0 3 6 9) + :min-distribution (make-array 4 :initial-contents '(35 25 20 20)) + :distribution (make-array 3 + :initial-contents '(6 1 1)))) + +(defparameter *augmented-chord* + (make-instance 'chord :label :aug :notes '(0 4 8) :min-distribution #(35 25 20 20) + :bass-likelihoods (make-array 12 + :initial-contents *aug-ratios*) + :distribution (make-array 12 + :initial-contents '(6 1 1 1 5 1 1 1 5 1 1 1)))) + +(defparameter *augmented-chord-short* + (make-instance 'chord :label :aug :notes '(0 4 8) + :distribution (make-array 4 + :initial-contents '(6 1 1 1)))) + +(defparameter *suspended4th-chord* + (make-instance 'chord :label :sus4 :notes '(0 5 7) + :min-distribution (make-array 4 :initial-contents '(35 25 20 20)) + :bass-likelihoods (make-array 12 + :initial-contents *sus-ratios*) + :distribution (make-array 12 + :initial-contents '(6 1 2 3 3 5 1 5 1 1 2 2)))) + +(defparameter *suspended9th-chord* + (make-instance 'chord :label :sus9 :notes '(0 2 7) + :min-distribution (make-array 4 :initial-contents '(35 25 20 20)) + :bass-likelihoods (make-array 12 + :initial-contents *sus-ratios*) + :distribution (make-array 12 + :initial-contents '(6 1 5 2 2 2 1 5 1 2 2 1)))) + +;; CHORDSETS +(defparameter *full-set* + (make-instance 'chordset :chords (list *major-chord* *minor-chord* + *diminished-chord* *augmented-chord* + *suspended4th-chord* *suspended9th-chord*))) + +(defparameter *full-set-variable-length* + (make-instance 'chordset :chords (list *major-chord* *minor-chord* + *diminished-chord-short* *augmented-chord-short* + *suspended4th-chord* *suspended9th-chord*))) + +(defparameter *partial-set* + (make-instance 'chordset :chords (list *major-chord* *minor-chord* + *diminished-chord* *augmented-chord*))) + +(defparameter *partial-set-variable-length* + (make-instance 'chordset + :chords (list *major-chord* *minor-chord* + *diminished-chord-short* + *augmented-chord-short*))) + +(defparameter *minimal-set* + (make-instance 'chordset :chords (list *major-chord* *minor-chord*))) + +#+nil +(defparameter *chord-proportions* ;; guess + (list (cons *major-chord* 17/30) (cons *minor-chord* 10/30) + (cons *diminished-chord* 1/60) (cons *augmented-chord* 1/60) + (cons *suspended4th-chord* 1/30) (cons *suspended9th-chord* 1/30))) + +#+nil +(defparameter *chord-proportions* ;; another guess + (list (cons *major-chord* 1/3) (cons *minor-chord* 1/3) + (cons *diminished-chord* 1/30) (cons *augmented-chord* 1/60) + (cons *suspended4th-chord* 1/5) (cons *suspended9th-chord* 1/12))) +#+nil +(defparameter *chord-proportions* ;; flat + (list (cons *major-chord* 1/6) (cons *minor-chord* 1/6) + (cons *diminished-chord* 1/6) (cons *augmented-chord* 1/6) + (cons *suspended4th-chord* 1/6) (cons *suspended9th-chord* 1/6))) + +(defparameter *chord-proportions* + ;; observed + ;; FIXME: This seriously impairs dim and aug. Do they ever get + ;; diagnosed now? + (list (cons *major-chord* 546/917) (cons *minor-chord* 312/917) + (cons *diminished-chord* 2/917) (cons *augmented-chord* 1/917) + (cons *suspended4th-chord* 44/917) (cons *suspended9th-chord* 12/917))) + + +;; First steps to chord labelling +(defun get-chord-likelihoods-for-model (anchored-period music + &key (model :gamma) + (chordset *full-set*)) + ;; Currently expects and returns an alist of (identifier + ;; . likelihood) (unnormalised, but can use normalise-likelihoods) + (ecase model + (:constant-prior + ;; results are divided by number of chords in chordset (times 12) + (constant-prior-likelihoods anchored-period + music + chordset)) + (:scaled-prior + ;; results are divided by preset chord weightings (times 12) + (scaled-prior-likelihoods anchored-period + music + chordset)) + (:naive-bass + (naive-bass-prior-likelihoods anchored-period + music + chordset)) + (:metrical-prior + (metrical-prior-likelihoods anchored-period + music + chordset)) + (:gamma + ;; dirichlet-based likelihood calculation + (3ple-gamma-likelihoods anchored-period + music + chordset)))) + +;; LIKELIHOOD-CALCULATION FUNCTIONS +(defgeneric metrical-prior-likelihoods (anchored-period music chordset)) +(defmethod metrical-prior-likelihoods ((anchored-period anchored-period) + music chordset) + (let* ((metrical-level (metrical-level-for-likelihood anchored-period music)) + (p (if (= metrical-level 1) + 0.51 + 0.07))) + (loop for chord in (chords chordset) + nconc (loop for i from 0 to 11 + collect (cons (list i chord) + (/ p (* 12 (length (chords chordset))))))))) + +(defgeneric constant-prior-likelihoods (anchored-period music chordset)) +(defmethod constant-prior-likelihoods ((anchored-period anchored-period) + music chordset) + ;; returns a flat distribution totalling 1 + (loop for chord in (chords chordset) + nconc (loop for i from 0 to 11 + collect (cons (list i chord) + (/ 1 (* 12 (length + (chords chordset)))))))) + +(defgeneric scaled-prior-likelihoods (anchored-period music chordset &key prior-alist)) +(defmethod scaled-prior-likelihoods ((anchored-period anchored-period) music chordset + &key (prior-alist *chord-proportions*)) + ;; returns a distribution based on the relative likelihood of chord types + (loop for chord in (chords chordset) + nconc (loop for i from 0 to 11 + collect (cons (list i chord) + (/ (cdr (assoc chord prior-alist)) + 12))))) + +(defgeneric naive-bass-prior-likelihoods (anchored-period music chordset)) +(defmethod naive-bass-prior-likelihoods ((anchored-period anchored-period) music chordset) + (let ((pc (bass-note anchored-period music))) + (loop for chord in (chords chordset) + nconc (loop for i from 0 to 11 + collect (cons (list i chord) + (/ (aref (bass-likelihoods chord) + (mod (+ i pc) 12)) + (length (chords chordset)))))))) + +(defgeneric 3ple-gamma-likelihoods (anchored-period music chordset)) +(defmethod 3ple-gamma-likelihoods ((anchored-period anchored-period) music chordset) + ;; Ask Christophe about what this one does - this function just + ;; provides data to his dirichlet likelihood functions. Currently + ;; limited to triads, this has two distributions for relative + ;; strengths of chord notes and for the relation between chord and + ;; non-chord notes. + (let ((pitch-classes (normalised-pitch-class-distribution anchored-period music)) + (metrical-level (metrical-level-for-likelihood anchored-period music)) + (likelihoods)) + (dolist (chord (chords chordset) likelihoods) + (let ((chord-likelihoods (subseq (min-distribution chord) 0 3)) + (non-chord (aref (min-distribution chord) 3))) + (loop for offset from 0 to 11 + do (setf likelihoods + (set-likelihood + likelihoods chord offset + (3ple-likelihood (chromatic-rotate pitch-classes (- offset)) + chord-likelihoods + non-chord + (main-notes chord) + metrical-level 1 + (get-alphas chord metrical-level :version :learned) + (get-betas chord metrical-level :version :learned))))))))) + +#+nil +(defun get-alphas (chord metrical-level &key (version :map)) + ;; MP values, map commented + (cond + ((or (eq *major-chord* chord) + (eq *minor-chord* chord)) + (cond + ((< metrical-level 1) + (case version + (:map #(3.7812 2.4955 2.1525)) + (:ml #(4.0398 2.6624 2.2942)) + (:learned #(2.0475 1.365 1.1374999)))) + (t + (case version + (:map #(3.6626 1.5234 2.3395)) + (:ml #(3.9119 1.6193 2.4955)) + (:learned #(2.0475 1.365 1.1374999)))))) + (t + (case version + (:map #(3.5110 2.0252 1.2963)) + (:ml #(4.0822 2.3459 1.4874)) + (:learned #(2.0475 1.365 1.1374999)))))) + +;; New, corrected ground truth +(defun get-alphas (chord metrical-level &key (version :map)) + ;; MP values, map commented + (cond + ((or (eq *major-chord* chord) + (eq *minor-chord* chord)) + (cond + ((< metrical-level 1) + (case version + (:map #(3.7397 2.4923 2.0187)) + (:ml #(3.9434 2.6253 2.1239)) + (:learned #(2.0475 1.365 1.1374999)))) + (t + (case version + (:map #(3.2620 1.3882 2.2542)) + (:ml #(3.5200 1.4889 2.4293)) + (:learned #(2.0475 1.365 1.1374999)))))) + (t + (case version + (:map #(3.1963 1.8187 1.3340)) + (:ml #(3.6371 2.0621 1.2799)) + (:learned #(2.0475 1.365 1.1374999)))))) + +#+nil +(defun get-betas (chord metrical-level &key (version :map)) + (cond + ((eq version :learned) + (cond + ((> metrical-level 1/2) + #(0.97 12)) + ((= metrical-level 1/2) + #(0.97 6)) + (t #(0.97 4)))) + ((or (eq *major-chord* chord) + (eq *minor-chord* chord)) + (if (< metrical-level 1) + (if (eq version :map) + #(0.6987 3.1724) + #(0.7164 3.2640)) + (if (eq version :map) + #(1.3677 5.9215) + #(1.4454 6.2843)))) + (t + (if (eq version :map) + #(0.9358 5.2212) + #(1.0431 5.8530))))) + +;; With new, corrected ground truth +(defun get-betas (chord metrical-level &key (version :map)) + (cond + ((eq version :learned) + (cond + ((> metrical-level 1/2) + #(0.97 12)) + ((= metrical-level 1/2) + #(0.97 6)) + (t #(0.97 4)))) + ((or (eq *major-chord* chord) + (eq *minor-chord* chord)) + (if (< metrical-level 1) + (if (eq version :map) + #(0.7041 3.3448) + #(0.7190 3.4260)) + (if (eq version :map) + #(1.3838 6.4581) + #(1.4872 6.9785)))) + (t + (if (eq version :map) + #(0.9558 5.0847) + #(1.0551 5.6740))))) + + +(defun chromatic-rotate (vector offset) + ;; transpose an n-member (chromatic) vector by an integral number of + ;; steps (semitones) + (let* ((size (length vector)) + (result (make-array size))) + (dotimes (i size result) + (setf (aref result i) (aref vector (mod (- i offset) size)))))) + +(defgeneric metrical-level-for-likelihood (anchored-period music)) +(defmethod metrical-level-for-likelihood (anchored-period (music composition)) + ;; metrical level is a function of time signature and window size + ;; and is used to modify the gamma function. + (let ((time-sigs (get-applicable-time-signatures anchored-period music))) + (cond + ((= (length time-sigs) 1) + (/ (duration anchored-period) + (crotchets-in-a-bar (first time-sigs)))) + ((null time-sigs) + ;; If, for some reason, we have no time-signature, midi specs + ;; say assume 4/4. + (/ (duration anchored-period) 4)) + (t + (loop for sig in time-sigs + sum (/ (duration (period-intersection sig + anchored-period)) + (crotchets-in-a-bar sig))))))) + +;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Hypothesis comparison / level navigation +;; + +(defun chord-labels (anchored-period music + &key (chordset *full-set*) + (models *default-models*)) + (let ((harmonic-analysis (best-level anchored-period music :chordset chordset :models models)) + (best-likelihood) (chord-labels)) + (do ((path (first harmonic-analysis) (cdr path)) + (likelihoods (second harmonic-analysis) (cdr likelihoods))) + ((null path) (reverse chord-labels)) + (dolist (likelihood (car likelihoods)) + (when (or (null best-likelihood) + (> (likelihood-likelihood likelihood) + (likelihood-likelihood best-likelihood))) + (setf best-likelihood likelihood))) + (push (cons (first path) (likelihood-chord best-likelihood)) chord-labels) + (setf best-likelihood nil)))) + +(defun best-level (anchored-period music + &key (chordset *full-set*) + (models *default-models*)) + ;; Takes a period for the largest time-unit being considered and + ;; returns the highest probability subdivision, its likelihood + ;; values (and the probability of that subdivision, but that's a bit + ;; of a coincidence and may want not to happen) + (best-level-hypothesis (make-metrical-divisions anchored-period music) + music :chordset chordset :models models)) + +(defgeneric make-metrical-divisions (anchored-period music)) +(defmethod make-metrical-divisions ((anchored-period anchored-period) + (music composition)) + ;; Prepares a set of divisions of the period based on time-sig and a + ;; pre-set list of options for each possible time-sig numerator. + (let ((time-sigs (get-applicable-time-signatures anchored-period music))) + (if + (< (length time-sigs) 2) + (let ((candidates)) + ;; get an appropriate set of divisions. Not sure this is right + ;; - it relies on bar position being irrelevant. Is this true? + ;; This isn't really clear from this code, but if there are no + ;; time-signatures, make-divisions-with-timesigs has a test for + ;; it and will pretend it's 4/4. + (dolist (divisions (make-divisions-with-time-signature anchored-period (car time-sigs)) + candidates) + (do ((time (onset anchored-period) (cut-off (car candidate-set))) + (divisions divisions (cdr divisions)) + (candidate-set)) + ((null divisions) (push (reverse candidate-set) candidates)) + (push (make-anchored-period (timepoint time) (first divisions)) + candidate-set)))) + ;; otherwise, there are lots. Run this function once for each + ;; time-signature. + (loop for time-sig in time-sigs + nconc (make-metrical-divisions (period-intersection anchored-period time-sig) + music))))) + +(defgeneric make-divisions-with-time-signature (period time-signature)) +(defmethod make-divisions-with-time-signature ((period period-designator) + (time-signature basic-time-signature)) + (let* ((numerator (time-signature-numerator time-signature)) + (denominator (time-signature-denominator time-signature)) + (path-options (cdr (assoc numerator *path-options*)))) + (loop for divisions in path-options + collect (period-fill period divisions denominator)))) + +(defmethod make-divisions-with-time-signature ((period period-designator) + time-signature) + ;; not a known time-signature type. Assume 4/4 + (let ((path-options (cdr (assoc 4 *path-options*)))) + (loop for divisions in path-options + collect (period-fill period divisions 4)))) + +(defun period-fill (period path-options denominator) + ;; take a division of the ?bar and then repeat it until the period + ;; is filled. + ;; + ;; Perhaps this and surrounding function need to make more use of + ;; time interface? + (let ((duration-list) + ;; Multiply path-options by unit of meter. + (path-options (map 'list + #'(lambda (x) (* x (/ 4 denominator))) + path-options))) + (do* ((circular-path path-options (or (cdr circular-path) + path-options)) + (current-duration (car circular-path) (car circular-path)) + (prev-remaining (duration period) remaining) + (remaining (- (duration period) current-duration) (- remaining current-duration))) + ((<= remaining 0) (reverse (cons prev-remaining duration-list))) + (push current-duration duration-list)))) + +(defun best-level-hypothesis (division-hypotheses music + &key (chordset *full-set*) + (models *default-models*)) + ;; Rather messy wrapper for level-hypothesis-likelihoods. Should + ;; probably make this a structure or something, but use looks like + ;; being quite limited. might revisit. + (first (sort (level-hypothesis-likelihoods division-hypotheses + music + :chordset chordset + :models models) + #'> :key #'third))) + +(defun level-hypothesis-likelihoods (division-hypotheses music + &key (chordset *full-set*) + (models *default-models*)) + ;; This function takes the candidate windows being considered (as + ;; lists of anchored periods) and, for each, works out likelihoods + ;; and the most probable hypothesis. This should come from taking + ;; the likelihoods and dividing by the product of the internal sums + ;; (don't ask!) + (let ((hypothesis-likelihoods)) + (dolist (hypothesis division-hypotheses hypothesis-likelihoods) + (let ((likelihoods (map 'list + #'(lambda (x) + (get-chord-likelihoods x music models chordset)) + hypothesis))) + (push (list hypothesis likelihoods (combined-likelihoods-sum likelihoods)) + hypothesis-likelihoods))))) + +(defun get-chord-likelihoods (anchored-period music models chordset) + (let ((model-likelihoods + (loop for model in models + collect (get-chord-likelihoods-for-model anchored-period + music + :model model + :chordset chordset)))) + (combine-multimodel-likelihoods model-likelihoods))) + +(defun combine-multimodel-likelihoods (likelihoods-list) + (cond + ((= (length likelihoods-list) 1) + (car likelihoods-list)) + (t + (let ((combined-likelihoods)) + (dolist (reference-likelihood (car likelihoods-list) combined-likelihoods) + (setf combined-likelihoods + (set-likelihood combined-likelihoods + (likelihood-chordtype reference-likelihood) + (likelihood-pitch-class reference-likelihood) + (apply #'* (loop for model-likelihoods in likelihoods-list + collect (likelihood-likelihood + (assoc (car reference-likelihood) + model-likelihoods + :test #'equal))))))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Likelihood (structure) manipulation and access methods +;; + +(defgeneric set-likelihood (likelihoods offset chord likelihood)) +(defmethod set-likelihood ((likelihoods list) chord offset likelihood) + (acons (list offset chord) likelihood likelihoods)) + +(defgeneric get-likelihood (likelihoods offset chord)) +(defmethod get-likelihood ((likelihoods list) offset chord) + (assoc (list offset chord) likelihoods :test #'equal)) + +(defgeneric best-n-likelihoods (n likelihoods)) +(defmethod best-n-likelihoods (n (likelihoods list)) + (let ((ranked (ordered-likelihoods likelihoods))) + (subseq ranked 0 n))) + +(defgeneric ordered-likelihoods (likelihoods)) +(defmethod ordered-likelihoods ((likelihoods list)) + (sort (copy-seq likelihoods) #'> :key #'cdr)) + +(defgeneric pretty-display-likelihoods (likelihoods)) +(defmethod pretty-display-likelihoods ((likelihoods list)) + (dolist (p likelihoods) + (format *standard-output* "~%~A~C~A~C~A" + (likelihood-key p) #\Tab + (chord-label (likelihood-chordtype p)) #\Tab + (likelihood-likelihood p)))) + +(defgeneric likelihood-key (likelihood)) +(defmethod likelihood-key ((likelihood list)) + (aref *keys* (first (first likelihood)))) + +(defgeneric likelihood-pitch-class (likelihood)) +(defmethod likelihood-pitch-class ((likelihood list)) + (first (first likelihood))) + +(defgeneric likelihood-chordtype (likelihood)) +(defmethod likelihood-chordtype ((likelihood list)) + (second (first likelihood))) + +(defgeneric likelihood-chord (likelihood)) +(defmethod likelihood-chord ((likelihood list)) + (first likelihood)) + +(defgeneric likelihood-likelihood (likelihood)) +(defmethod likelihood-likelihood ((likelihood list)) + (cdr likelihood)) + +(defgeneric likelihoods-sum (likelihoods)) +(defmethod likelihoods-sum ((likelihoods list)) + (loop for likelihood in likelihoods + sum (likelihood-likelihood likelihood))) + +(defgeneric combined-likelihoods-sum (combined-likelihoods)) +(defmethod combined-likelihoods-sum ((combined-likelihoods list)) + ;; Will be needed for hypothesis comparison - sums the likelihoods + ;; for all chords within a window for multiple likelihood + ;; calculations + (apply #'* (map 'list #'(lambda (window) + (loop for likelihood in window + sum (likelihood-likelihood likelihood))) + combined-likelihoods))) + +(defgeneric normalise-likelihoods (likelihoods)) +(defmethod normalise-likelihoods ((likelihoods list)) + (let ((p-sum (sum-likelihoods likelihoods))) + (if (= p-sum 1) + likelihoods + (scale-likelihoods likelihoods (/ 1 p-sum))))) + +(defgeneric scale-likelihoods (likelihoods scale-factor)) +(defmethod scale-likelihoods ((likelihoods list) (scale-factor number)) + (map 'list #'(lambda (x) + (cons (first x) + (* (cdr x) scale-factor))) + likelihoods)) + +(defgeneric sum-likelihoods (likelihoods)) +(defmethod sum-likelihoods ((likelihoods list)) + (apply #'+ (map 'list #'cdr likelihoods))) + +;; Probably useless vestigial stuff from here + +(defun vector-list-apply (predicate vector-list &optional other-args) + (let ((result-list)) + (dolist (vector vector-list (reverse result-list)) + (push (make-array (array-dimensions vector)) result-list) + (loop for i from 0 to (1- (length vector)) + do (setf (aref (first result-list) i) + (apply predicate (cons (aref vector i) other-args))))))) + +(defun vector-sum (vector) + (loop for i from 0 to (1- (length vector)) + sum (aref vector i))) + +(defun make-flat-result (chordset) + (map 'list #'(lambda (x) + (make-array (length (distribution x)) + :initial-element 0)) + (chords chordset))) + +(defun key-name (pitch-class) + (if pitch-class + (aref #("C" "C#" "D" "Eb" "E" "F" "F#" "G" "G#" "A" "Bb" "B") (mod pitch-class 12)) + nil)) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/harmony/classes.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,57 @@ +(in-package #:amuse-harmony) + +;; This file contains classes for use in the harmony module. There are +;; two sets of classes here: those used in the chord-labelling itself, +;; and those used for label-evaluation and comparison with ground +;; truth. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; CHORD LABELLING +;; +;; Chord objects are the fundamental object for identifying and naming +;; chord types they also give info about pitch classes and +;; distribution. + +(defclass chord () + ((label :accessor chord-label + :initarg :label) + (main-notes :accessor main-notes + :initarg :notes) + (bass-likelihoods :accessor bass-likelihoods + :initarg :bass-likelihoods) + (min-distribution :accessor min-distribution + :initarg :min-distribution) + (distribution :accessor distribution + :initarg :distribution) + (normalised-distribution :initarg :normalised-distribution + :initform nil))) + +;; A chordset is a gathering of chords for experiment. There are slots +;; for priors, but I'm not using them at the moment (see below for +;; current, not very good, method). + +(defclass chordset () + ((chords :accessor chords + :initarg :chords + :initform nil) + (priors :accessor priors + :initarg :priors + :initform nil))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; EVALUATION +;; + +(defclass labelled-chord-period (anchored-period) + ((root-pitch-class :writer %labelled-chord-root + :reader labelled-chord-root + :initarg :root) + (chord-type :writer %labelled-chord-type + :reader labelled-chord-type + :initarg :chord-type) + (bass :writer %labelled-chord-bass + :reader labelled-chord-bass + :initarg :bass))) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/harmony/evaluation.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,882 @@ +;; Stuff to compare: path (per model); correct window (per model); correct window|bass; correct window|bass + +(in-package #:amuse-harmony) + +(defparameter *test-pieces* '()) +(defparameter *test-set* '()) +(defparameter *dm-note-names* '("c" "c+" "d" "e-" "e" "f" "f+" "g" "g+" "a" "b-" "b")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Ground-truth data types and functions +;; + +(clsql:def-view-class truth-chord () + ((cat_id + :type integer + :accessor cat_id) + (onset_bar + :type integer + :accessor start-bar) + (onset_beat + :type integer + :accessor start-beat) + (root + :type string) + (bass + :type string) + (chord-type + :type string + :accessor chord-type) + (file + :accessor harmony-file + :db-kind :join + :db-info (:join-class file + :home-key cat_id + :foreign-key cat_id + :set nil))) + (:base-table geerdes_harmony)) + +(defun all-chords () + #.(clsql:locally-enable-sql-reader-syntax) + (map 'list #'car (clsql:select 'truth-chord :where 1)) + #.(clsql:restore-sql-reader-syntax-state)) + +(defun list-harmonised-pieces () + (remove-if #'(lambda (x) (member x '(886 5205 10586 3473 6993))) + (remove-duplicates (map 'list + #'cat_id + (all-chords))))) + +(defun harmonised-bars (id) + ;; List bars which have harmonies from piece with cat_id=id + (let* ((piece-chords (piece-chord-list id))) + (when piece-chords + (let* ((chord-starts (map 'list #'start-bar piece-chords)) + (bar-max (1+ (apply #'max chord-starts))) + (bar-min (apply #'min chord-starts)) + (bar-numbers)) + (setf piece-chords (sort piece-chords #'chord-time->)) + (do ((bar bar-min (1+ bar))) + ((= bar bar-max) bar-numbers + #+nil (make-array (length bar-numbers) + :initial-contents (reverse bar-numbers))) + (let ((harmony (get-applicable-chord (* 4 bar) piece-chords))) + (when (and harmony + (> (length (chord-type harmony)) 0)) + (push bar bar-numbers)))))))) + +(defun get-applicable-chord (beats chords &key (pre-sorted t)) + ;; Return the chord that would be sounding at a given point in + ;; time. If this is being applied many times, it makes sense to sort + ;; the data before providing it, hence the &key argument. + (unless pre-sorted + (setf chords (sort chords #'chord-time->))) + (find-if #'(lambda (x) (or (< (start-bar x) + (floor beats 4)) + (and (= (start-bar x) + (floor beats 4)) + (<= (start-beat x) + (1+ (mod beats 4)))))) + chords)) + +(defun chord-time-> (chord1 chord2) + (or (> (start-bar chord1) + (start-bar chord2)) + (and (= (start-bar chord1) + (start-bar chord2)) + (> (start-beat chord1) + (start-beat chord2))))) + +(defun chord-time-< (chord1 chord2) + (or (< (start-bar chord1) + (start-bar chord2)) + (and (= (start-bar chord1) + (start-bar chord2)) + (< (start-beat chord1) + (start-beat chord2))))) + +(defun harmonised-pieces-bars-alist () + (let ((harmonised)) + (dolist (piece (list-harmonised-pieces) harmonised) + (setf harmonised (acons piece (harmonised-bars piece) harmonised))))) + +(defun all-bars () + (let ((harmonised)) + (dolist (piece (list-harmonised-pieces) harmonised) + (dolist (bar (harmonised-bars piece)) + (push (cons piece bar) harmonised))))) + +(defun random-bars (target) + (let* ((selection) (pieces-bars (make-array 1 :adjustable T :fill-pointer T))) + (dolist (piece (list-harmonised-pieces)) + (loop for bar in (harmonised-bars piece) + do (vector-push-extend (cons piece bar) pieces-bars))) + (loop for i from 0 to (1- target) + do (progn + (rotatef (aref pieces-bars i) + (aref pieces-bars + (+ i (random (- (1- (length pieces-bars)) + i))))) + (push (aref pieces-bars i) selection))) + selection)) + +(defun write-piece-bars (list filename) + (with-open-file (stream filename :direction :output :if-exists :supersede) + (dolist (bar list) + (format stream "~A~C~A~%" (car bar) #\Tab (cdr bar))))) + +(defun read-piece-bars (filename) + (let ((bars)) + (with-open-file (stream filename) + (do ((line (read-line stream nil nil) (read-line stream nil nil))) + ((null line) bars) + (let* ((s (make-string-input-stream line)) + (piece (read s)) + (bar (read s))) + (push (cons piece bar) bars)))))) + +(defun get-window-sizes (piece bar &optional (odd-divisions nil)) + (let ((bar-chords (sort (remove-if #'(lambda (x) (or (not (= (cat_id x) piece)) + (not (= (start-bar x) bar)))) + (all-chords)) + #'chord-time-<))) + (if odd-divisions + (get-window-sizes-2 bar-chords) + (get-window-sizes-1 bar-chords)))) + +(defun get-window-sizes-2 (bar-chords) + (let ((path) (prev-chord)) + (dolist (beat-chord bar-chords) + (cond + (prev-chord + (push (- (start-beat beat-chord) + (start-beat prev-chord)) + path)) + ((and (not prev-chord) + (> (start-beat beat-chord) 1)) + (push (- (start-beat beat-chord) 1) + path))) + (setf prev-chord beat-chord)) + (if prev-chord + (push (- 5 (start-beat prev-chord)) + path) + (setf path '(4))) + (reverse path))) + +(defun get-window-sizes-1 (bar-chords) + (let ((path) (prev-chord)) + (dolist (beat-chord bar-chords) + (cond + ((and (not prev-chord) + (= (start-beat beat-chord) 4)) + ;; Three beats of preceding chord or no chord + (return-from get-window-sizes-1 '(2 1 1))) + ((and (not prev-chord) + (> (start-beat beat-chord) 1)) + ;; not the first chord of bar - just the first to sound + (push (1- (start-beat beat-chord)) path)) + ((and (= (start-beat beat-chord) 4) + (< (start-beat prev-chord) 3)) + ;; Last chord of bar, but its predecessor spans half-bar + ;; break. + (return-from get-window-sizes-1 (reverse (concatenate 'list + (list 1 1 (- 3 (start-beat prev-chord))) + path)))) + ((= (start-beat beat-chord) 4) + ;; last chord of bar + (return-from get-window-sizes-1 (reverse (concatenate 'list + (list 1 1) + path)))) + (prev-chord + (push (- (start-beat beat-chord) (start-beat prev-chord)) + path))) + (setf prev-chord beat-chord)) + (if prev-chord + (if (= (start-beat prev-chord) 2) + '(1 1 2) + (reverse (cons (- 5 (start-beat prev-chord)) path))) + '(4)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun get-test-piece (cat-id) + (unless (assoc cat-id *test-pieces*) + (setf *test-pieces* (acons cat-id + (get-composition (make-geerdes-cat-identifier cat-id)) + *test-pieces*))) + (cdr (assoc cat-id *test-pieces*))) + +(in-package "AMUSE-GEERDES") + +(defclass harmonic-evaluation-period (anchored-period) + ((cat-id :accessor %cat-id + :initarg :cat-id) + (file-id :accessor %file-id + :initarg :file-id) + (composition :accessor %composition + :initarg :composition) + (bar-number :accessor %bar-number + :initarg :bar-number) + (composition-bar-number :accessor %c-bar-number + :initarg :comp-bar-number) + (ground-truth-chords :accessor %gt-chords + :initarg :gt-chords + :initform nil) + (derived-windows :accessor %d-windows + :initarg :d-windows + :initform nil) + (derived-chords :accessor %d-chords + :initarg :d-chords + :initform nil) + (derived-likelihoods :accessor %d-likelihoods + :initarg :d-likelihoods + :initform nil))) + +(defun reset-harmonisation (harmonisation) + (setf (%d-chords harmonisation) nil + (%d-windows harmonisation) nil + (%d-likelihoods harmonisation) nil)) + +(defun derived-likelihoods (harmonisation &key (models *default-models*) + (chordset *full-set*)) + (if (%d-likelihoods harmonisation) + (%d-likelihoods harmonisation) + (let* ((possible-window-combinations (make-metrical-divisions harmonisation + (%composition harmonisation))) + (possible-windows (remove-duplicates (apply #'nconc possible-window-combinations) + :test #'period=))) + (setf (%d-likelihoods harmonisation) + (loop for window in possible-windows + collect (cons window + (get-chord-likelihoods window + (%composition harmonisation) + models chordset))))))) +(defun ground-truth-window-sizes (harmonisation) + (let* ((chords (ground-truth-chords harmonisation)) + (bar-number (%bar-number harmonisation)) + (beats (nconc (map 'list #'(lambda (x) + (if (= (start-bar x) + bar-number) + (start-beat x) + 1)) + chords) + (list (1+ (duration harmonisation)))))) + (map 'list #'- (cdr beats) beats))) + + + +(defun ground-truth-chords (harmonisation) + (if (%gt-chords harmonisation) + (%gt-chords harmonisation) + (let ((piece-chords (sort (piece-chord-list (%cat-id harmonisation)) + #'chord-time->)) (gt-chords)) + (setf (%gt-chords harmonisation) + (dolist (pc piece-chords gt-chords) + (cond + ((= (start-bar pc) (%bar-number harmonisation)) + (push pc gt-chords) + (when (= (start-beat pc) 1) + (return gt-chords))) + ((< (start-bar pc) (%bar-number harmonisation)) + (return (cons pc gt-chords))))))))) + +(defun derived-window-sizes (harmonisation &key (chordset *full-set*) models) + (declare (ignore models)) + (unless (%d-windows harmonisation) + (let ((level (best-level harmonisation (%composition harmonisation) + :chordset chordset))) + (setf (%d-chords harmonisation) + (loop for likelihoods in (second level) + collect (likelihood-chord + (car (best-n-likelihoods 1 likelihoods)))) + (%d-windows harmonisation) (first level)))) + (map 'list #'duration (%d-windows harmonisation))) + +(defun derived-windows (harmonisation &key (chordset *full-set*) models) + (declare (ignore models)) + (unless (%d-windows harmonisation) + (let ((level (best-level harmonisation (%composition harmonisation) + :chordset chordset))) + (setf (%d-chords harmonisation) + (loop for likelihoods in (second level) + collect (likelihood-chord + (car (best-n-likelihoods 1 likelihoods)))) + (%d-windows harmonisation) (first level)))) + (%d-windows harmonisation)) + +(defun derived-chords (harmonisation &key (chordset *full-set*) models) + (declare (ignore models)) + (cond + ((%d-chords harmonisation) + (%d-chords harmonisation)) + (t (let ((level (best-level harmonisation (%composition harmonisation) + :chordset chordset))) + (setf (%d-windows harmonisation) (first level) + (%d-chords harmonisation) + (loop for likelihoods in (second level) + collect (likelihood-chord (car (best-n-likelihoods 1 likelihoods))))))))) + +(defparameter *harmonic-evaluation-period-cache* nil) +(defun get-harmonic-evaluation-period (bar-number &key cat-id file-id composition) + (cond + (cat-id (get-harmonic-evaluation-period-by-cat-id bar-number cat-id)) + (file-id (get-harmonic-evaluation-period-by-file-id bar-number file-id)) + (composition (get-harmonic-evaluation-period-by-composition bar-number composition)))) + +(defun get-harmonic-evaluation-period-by-cat-id (bar-number cat-id) + (let ((harmonisation (find-if #'(lambda (x) + (and (= (%cat-id x) + cat-id) + (= (%bar-number x) + bar-number))) + *harmonic-evaluation-period-cache*))) + (unless harmonisation + (setf harmonisation + (make-harmonic-evaluation-period bar-number + (get-test-piece cat-id))) + (push harmonisation *harmonic-evaluation-period-cache*)) + harmonisation)) + +(defun get-harmonic-evaluation-period-by-file-id (bar-number file-id) + (let ((harmonisation (find-if #'(lambda (x) + (and (= (%file-id x) + file-id) + (= (%bar-number x) + bar-number))) + *harmonic-evaluation-period-cache*))) + (unless harmonisation + (let* ((composition (get-composition (make-geerdes-identifier file-id)))) + (setf harmonisation (make-harmonic-evaluation-period bar-number + composition)) + (push harmonisation *harmonic-evaluation-period-cache*))) + harmonisation)) + +(defun get-harmonic-evaluation-period-by-composition (bar-number composition) + (let ((harmonisation (find-if #'(lambda (x) + (and (eq (%composition x) + composition) + (= (%bar-number x) + bar-number))) + *harmonic-evaluation-period-cache*))) + (unless harmonisation + (setf harmonisation (make-harmonic-evaluation-period bar-number + composition)) + (push harmonisation *harmonic-evaluation-period-cache*)) + harmonisation)) + +(defun make-harmonic-evaluation-period (bar-number composition) + (with-slots (cat_id id) + (%db-entry composition) + (let ((harmonisation (make-instance 'harmonic-evaluation-period + :cat-id cat_id + :file-id id + :composition composition + :bar-number bar-number + :comp-bar-number (1+ bar-number))) + (period (whole-bar-period (1+ bar-number) composition))) + (setf (timepoint harmonisation) (timepoint period) + (duration harmonisation) (duration period)) + harmonisation))) + +(defun verify-chord (truth-chord derived-chord) + (let ((gt-root (pitch-class-from-gt (slot-value truth-chord 'root))) + (gt-label (chord-from-gt (chord-type truth-chord))) + (d-root (first derived-chord)) + (d-label (chord-label (second derived-chord)))) + (samechordp gt-root gt-label d-root d-label))) + +(defun get-chord (anchored-period harmonisation) + (likelihood-chord (first (best-n-likelihoods 1 + (cdr (assoc anchored-period + (derived-likelihoods harmonisation) + :test #'period=)))))) + +(defun ground-truth-window-beats (harmonisation) + (let ((beat 1) (beat-list '(1))) + (dolist (size (ground-truth-window-sizes harmonisation) (reverse (cdr beat-list))) + (push (incf beat size) beat-list)))) + +(defun find-matching-period (onset duration period-list) + (find-if #'(lambda (x) (and (= onset (timepoint x)) + (= duration (duration x)))) + period-list)) + +(defun position-matching-period (onset duration period-list) + (position-if #'(lambda (x) (and (= onset (timepoint x)) + (= duration (duration x)))) + period-list)) + +(defun compare-paths (bar piece &key (chordset *full-set*) models controlp mergep) + ;; FIXME: Recreate controlp and mergep + (declare (ignore controlp mergep models)) + (let* ((harmonisation (get-harmonic-evaluation-period bar :cat-id piece)) + (gtw-beats (ground-truth-window-beats harmonisation)) + (gtw-sizes (ground-truth-window-sizes harmonisation)) + (score (loop for i from 0 to (1- (length gtw-beats)) + count (find-matching-period (+ (1- (nth i gtw-beats)) + (timepoint harmonisation)) + (nth i gtw-sizes) + (derived-windows harmonisation + :chordset chordset))))) + (values (= score (length gtw-sizes)) score (length gtw-sizes)))) + +(defun compare-paths-and-harmonies (bar piece &key (chordset *full-set*) models controlp mergep) + ;; FIXME: Recreate controlp and mergep + (declare (ignore controlp mergep models)) + (let* ((harmonisation (get-harmonic-evaluation-period bar :cat-id piece)) + (gtw-beats (ground-truth-window-beats harmonisation)) + (gtw-sizes (ground-truth-window-sizes harmonisation)) + (g-chords (ground-truth-chords harmonisation)) + (d-chords (derived-chords harmonisation)) + (d-windows (derived-windows harmonisation :chordset chordset)) + (score (loop for i from 0 to (1- (length gtw-beats)) + count (let ((matchesp (position-matching-period (+ (1- (nth i gtw-beats)) + (timepoint harmonisation)) + (nth i gtw-sizes) + d-windows))) + (and matchesp + (verify-chord (nth i g-chords) + (nth matchesp d-chords))))))) + (values (= score (length gtw-sizes)) score (length gtw-sizes)))) + +(defun compare-harmonies-with-gt-windows (bar piece &key (chordset *full-set*) models) + (declare (ignore chordset models)) + (let* ((harmonisation (get-harmonic-evaluation-period bar :cat-id piece)) + (ground-truth (ground-truth-chords harmonisation)) + (score 0)) + (do ((gt-chords ground-truth (cdr gt-chords))) + ((null gt-chords) (values (= score (length ground-truth)) + score + (length ground-truth))) + (when (verify-chord (first gt-chords) + (get-chord (make-anchored-period (+ (timepoint harmonisation) + (1- (start-beat (first gt-chords)))) + (- (if (second gt-chords) + (start-beat (second gt-chords)) + (duration harmonisation)) + (1- (start-beat (first gt-chords))))) + harmonisation)) + (incf score))))) + +(defun compare-harmonies-by-beat (bar piece &key (chordset *full-set*) models) + (declare (ignore chordset models)) + (let* ((harmonisation (get-harmonic-evaluation-period bar :cat-id piece)) + (score (loop for i from 1 to (duration harmonisation) + count (verify-chord (get-gt-chord-by-beat i harmonisation) + (get-derived-chord-by-beat i harmonisation))))) + (values (= score (duration harmonisation)) score (duration harmonisation)))) + +(defun get-gt-chord-by-beat (beat harmonisation) + (let ((chords (ground-truth-chords harmonisation))) + (do ((gt (reverse chords) (cdr gt))) + ((null gt) (car chords)) + (when (<= (start-beat (car gt)) beat) + (return-from get-gt-chord-by-beat + (car gt)))))) + +(defun get-derived-chord-by-beat (beat harmonisation) + (let ((total-beat (+ (1- beat) + (timepoint harmonisation))) + (chords (derived-chords harmonisation)) + (windows (derived-windows harmonisation))) + (nth (position-if #'(lambda (x) (and (>= total-beat + (timepoint x)) + (< total-beat + (timepoint (cut-off x))))) + windows) + chords))) + +(defparameter *param-estimation-numbers* + (list (cons :major (make-array '(4 21))) + (cons :minor (make-array '(4 21))) + (cons :dim (make-array '(4 21))) + (cons :aug (make-array '(4 21))) + (cons :sus4 (make-array '(4 21))) + (cons :sus9 (make-array '(4 21))))) + +(defun incf-stats (size type offset piece bar distribution) + (let* ((param (cdr (assoc type *param-estimation-numbers*))) + (chord-notes (main-notes (find type (chords *full-set*) :key #'chord-label))) + (full-total (reduce #'+ distribution)) + (chord-sum (loop for scale-deg in chord-notes + sum (aref distribution (mod (+ offset scale-deg) 12))))) + (when (= full-total 0) + (return-from incf-stats)) + (when (> chord-sum full-total) + (format *standard-output* + "~%Piece ~D, Bar ~D, distribution ~D, ~D ~D~%" + piece bar distribution offset type) + (error "BRRRRROKEN!")) + (when (<= chord-sum 1/4) + (format *standard-output* + "~%Piece ~D, Bar ~D, distribution ~D, ~D ~D~%" + piece bar distribution offset type) + (return-from incf-stats)) + #+nil (when (<= (aref distribution (mod (+ offset (nth 1 chord-notes)) 12)) 1/40) + (format *standard-output* "~%******Piece ~D, Bar ~D. distribution ~D, ~D ~D" + piece bar distribution offset type) + (return-from incf-stats)) + (when (<= (aref distribution offset) 1/10) + (format *standard-output* "~%Piece ~D, Bar ~D. distribution ~D, ~D ~D" + piece bar distribution offset type) + (return-from incf-stats)) + (let* ((chord-ratio (/ chord-sum full-total)) + (chord-squared (* chord-ratio chord-ratio)) + (chord-logged (log chord-ratio)) + (non-chord (- 1 chord-ratio)) + (non-chord-squared (* non-chord non-chord)) + (non-chord-logged (log non-chord)) + (d1 (/ (aref distribution (mod (+ offset (nth 0 chord-notes)) 12)) + chord-sum)) + (d1-squared (* d1 d1)) + (d1-logged (log d1)) + (d3 (/ (aref distribution (mod (+ offset (nth 1 chord-notes)) 12)) + chord-sum)) + (d3-squared (* d3 d3)) + (d3-logged (log d3)) + (d5 (/ (aref distribution (mod (+ offset (nth 2 chord-notes)) 12)) + chord-sum)) + (d5-squared (* d5 d5)) + (d5-logged (log d5))) + ;; n + (incf (aref param size 0)) + (incf (aref param size 1) chord-ratio) + (incf (aref param size 2) chord-squared) + (if (= chord-ratio 0) + (incf (aref param size 4)) + (incf (aref param size 3) chord-logged)) + (incf (aref param size 5) non-chord) + (incf (aref param size 6) non-chord-squared) + (if (= non-chord 0) + (incf (aref param size 8)) + (incf (aref param size 7) non-chord-logged)) + (incf (aref param size 9) d1) + (incf (aref param size 10) d1-squared) + (if (= d1 0) + (incf (aref param size 12)) + (incf (aref param size 11) d1-logged)) + (incf (aref param size 13) d3) + (incf (aref param size 14) d3-squared) + (if (= d3 0) + (incf (aref param size 16)) + (incf (aref param size 15) d3-logged)) + (incf (aref param size 17) d5) + (incf (aref param size 18) d5-squared) + (if (= d5 0) + (incf (aref param size 20)) + (incf (aref param size 19) d5-logged))))) + +(defun parameter-estimation-figures (&key (test-set *test-set*)) + (let ((size 0) (beat 0) + (c-type) (offset 0) + (harmonisation) (bar 0) (piece 0)) + (dolist (test test-set) + (setf piece (car test) + bar (cdr test) + harmonisation (get-harmonic-evaluation-period bar :cat-id piece)) + (do ((windows (ground-truth-chords harmonisation) (cdr windows)) + (window-sizes (ground-truth-window-sizes harmonisation) (cdr window-sizes)) + (window-beats (ground-truth-window-beats harmonisation) (cdr window-beats))) + ((not windows)) + (setf size (1- (first window-sizes)) + c-type (chord-from-gt (chord-type (first windows))) + offset (pitch-class-from-gt (slot-value (first windows) 'root)) + beat (+ (1- (first window-beats)) (timepoint harmonisation))) + (incf-stats size c-type offset piece bar + (pitch-class-distribution (make-anchored-period beat (1+ size)) + (%composition harmonisation))))))) + + +(defun write-numbers-to-file (pathname) + (with-open-file (s pathname :direction :output :if-exists :supersede) + (dolist (acns *param-estimation-numbers*) + (let ((c-type (car acns)) (data (cdr acns))) + (dotimes (i 4) + (format s "~D chords, ~D beats: ~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~C~D~%" + c-type (1+ i) + (aref data i 0) #\Tab + (float (aref data i 1)) #\Tab (float (aref data i 2)) #\Tab + (float (aref data i 3)) #\Tab (aref data i 4) #\Tab + (float (aref data i 5)) #\Tab (float (aref data i 6)) #\Tab + (float (aref data i 7)) #\Tab (aref data i 8) #\Tab + (float (aref data i 9)) #\Tab (float (aref data i 10)) #\Tab + (float (aref data i 11)) #\Tab (aref data i 12) #\Tab + (float (aref data i 13)) #\Tab (float (aref data i 14)) #\Tab + (float (aref data i 15)) #\Tab (aref data i 16) #\Tab + (float (aref data i 17)) #\Tab (float (aref data i 18)) #\Tab + (float (aref data i 19)) #\Tab (aref data i 20))))))) + +(defun write-numbers-to-file-2 (pathname) + (with-open-file (s pathname :direction :output :if-exists :supersede) + (dolist (acns *param-estimation-numbers*) + (let ((c-type (car acns)) (data (cdr acns))) + (dotimes (i 4) + (format s ";; ~D chords - ~D beats ~%((~D ~D) (~D ~D) (~D ~D) (~D ~D) ~D)~%((~D ~D ~D) (~D ~D ~D) (~D ~D ~D) (~D ~D ~D) ~D)~%" + c-type (1+ i) + ;; chord / non-chord (x, x^2, log(x), left out, n) + (float (aref data i 1)) (float (aref data i 5)) + (float (aref data i 2)) (float (aref data i 6)) + (float (aref data i 3)) (float (aref data i 7)) + (aref data i 4) (aref data i 8) + (aref data i 0) + ;; 1 / 3 / 5 (x, x^2, log(x), left out, n) + (float (aref data i 9)) (float (aref data i 13)) (float (aref data i 17)) + (float (aref data i 10)) (float (aref data i 14)) (float (aref data i 18)) + (float (aref data i 11)) (float (aref data i 15)) (float (aref data i 19)) + (aref data i 12) (aref data i 16) (aref data i 20) + (aref data i 0))))))) + +(defun test-paths (&key (test-set *test-set*)) + (let ((bars 0) (grand-score 0) (grand-total 0)) + (loop for test in test-set + do (when (goodness-test (cdr test) (car test)) + (multiple-value-bind (match score total) + (compare-paths (cdr test) (car test)) + (when match (incf bars)) + (incf grand-score score) + (incf grand-total total)))) + (values bars grand-score grand-total))) + +(defparameter *ignored* nil) +(defun goodness-test (bar piece) + ;; exclusions bin + (let* ((harmonisation (get-harmonic-evaluation-period bar :cat-id piece)) + (sounding-sum (reduce #'+ (pitch-class-distribution harmonisation (%composition harmonisation)))) + (pcd)) + ;; Too little sounding + (unless (> sounding-sum 1/4) + (push (list bar piece) *ignored*) + (return-from goodness-test nil)) + ;; per gt window tests + (do ((window-beats (ground-truth-window-beats harmonisation) (cdr window-beats)) + (window-sizes (ground-truth-window-sizes harmonisation) (cdr window-sizes)) + (gt-chords (ground-truth-chords harmonisation) (cdr gt-chords))) + ((null window-beats) t) + (setf pcd (pitch-class-distribution (make-anchored-period (+ (timepoint harmonisation) + (1- (first window-beats))) + (first window-sizes)) + (%composition harmonisation))) + (unless (and (> (reduce #'+ pcd) 1) + (> (aref pcd (pitch-class-from-gt (slot-value (first gt-chords) 'root))) + 1/16)) + (push (list bar piece) *ignored*) + (return-from goodness-test nil))))) + +(defun test-harmonies (&key (test-set *test-set*)) + ;; FIXME: whole bars only at the mo! + (loop for test in test-set + count (when (goodness-test (cdr test) (car test)) + (compare-harmonies-with-gt-windows (cdr test) (car test))))) + +(defun test-paths-and-chords (&key (test-set *test-set*)) + (let ((bars 0) (grand-score 0) (grand-total 0)) + (loop for test in test-set + do (when (goodness-test (cdr test) (car test)) + (multiple-value-bind (match score total) + (compare-paths-and-harmonies (cdr test) (car test)) + (when match (incf bars)) + (incf grand-score score) + (incf grand-total total)))) + (values bars grand-score grand-total))) + +(defun test-harmonies-by-window (&key (test-set *test-set*)) + (let ((bars 0) (grand-score 0) (grand-total 0)) + (loop for test in test-set + do (when (goodness-test (cdr test) (car test)) + (multiple-value-bind (match score total) + (compare-harmonies-with-gt-windows (cdr test) (car test)) + (when match (incf bars)) + (incf grand-score score) + (incf grand-total total)))) + (values bars grand-score grand-total))) + +(defun test-harmonies-by-beat (&key (test-set *test-set*)) + (let ((bars 0) (grand-score 0) (grand-total 0)) + (loop for test in test-set + do (when (goodness-test (cdr test) (car test)) + (multiple-value-bind (match score total) + (compare-harmonies-by-beat (cdr test) (car test)) + (when match (incf bars)) + (incf grand-score score) + (incf grand-total total)))) + (values bars grand-score grand-total))) + +(defgeneric whole-bar-period (bar-number composition)) +(defmethod whole-bar-period (bar-number (composition geerdes-midi-composition)) + (multiple-value-bind (beat-no timesig) + (bar-number-to-beats bar-number composition) + (make-anchored-period (timepoint beat-no) (crotchets-in-a-bar timesig)))) + +(defgeneric bar-number-to-beats (bar-number composition)) +(defmethod bar-number-to-beats (bar-number (composition geerdes-midi-composition)) + (do* ((time-sig-list (time-signatures composition) (cdr time-sig-list)) + (current-sig (car time-sig-list) (car time-sig-list)) + (beats-per-bar (make-floating-period (crotchets-in-a-bar current-sig)) + (make-floating-period (crotchets-in-a-bar current-sig))) + (bars-left bar-number)) + ((time>= (cut-off current-sig) + (time+ (onset current-sig) + (duration* beats-per-bar bars-left))) + (values (time+ (onset current-sig) + (duration* beats-per-bar bars-left)) + current-sig)) + (decf bars-left (duration/ current-sig beats-per-bar)))) + +(defun samechordp (root1 label1 root2 label2) + (or (and (= root1 root2) + (eq label1 label2)) + (and (eq label1 :sus4) + (eq label2 :sus9) + (= root2 (mod (+ root1 5) 12))) + (and (eq label2 :sus4) + (eq label1 :sus9) + (= root1 (mod (+ root2 5) 12))))) + +(defun chord-from-gt (string) + (cdr (assoc string '(("maj" . :major) ("min" . :minor) + ("dim" . :dim) ("aug" . :aug) + ("sus4" . :sus4) ("sus9" . :sus9)) + :test #'equal))) + +(defun pitch-class-from-gt (string) + (position-if #'(lambda (x) (string-equal x string)) + *dm-note-names*)) + +(defun piece-chord-list (id) + (remove-if #'(lambda (x) + (not (= (cat_id x) id))) + (all-chords))) + +(defun get-gt-bar-chords (piece bar) + (let ((bar-beats (* 4 bar)) + (chord-list (sort (piece-chord-list piece) + #'chord-time->))) + (loop for i from bar-beats to (+ 3 bar-beats) + collect (gt-chord-to-list (get-applicable-chord i chord-list))))) + +(defun gt-chord-to-list (chord) + (list (pitch-class-from-gt (slot-value chord 'root)) + (chord-from-gt (chord-type chord)))) + +(defun explore-parameters (&key (alpha-scale '(0.4 3)) (beta '(4 14))) + (let* ((original-alpha *alpha*) + (original-betas *betas*) + (results (loop for i from (first alpha-scale) to (second alpha-scale) by 0.3 + collect (progn + (setf *alpha* (map 'vector + #'(lambda (x) + (* x i)) + original-alpha)) + (print *alpha*) + (list i (explore-betas beta)))))) + (setf *alpha* original-alpha + *betas* original-betas) + results)) + +(defun explore-betas (beta) + (let* ((b1 (first beta)) + (bn (second beta)) + (n (- bn b1))) + (assert (equal (array-dimensions *results*) + (list n n n n 4))) + (loop for semi from 0 to (1- n) by 2 + do (progn + (format t "|~D |" (+ semi b1)) + (loop for dotted-minim from 0 to (1- n) by 2 + do (loop for minim from 0 to (1- n) by 2 + do (loop for crotchet from 0 to (1- n) by 2 + do (progn + (setf *harmonic-evaluation-period-cache* nil + *betas* (list (cons 1 (+ b1 semi)) + (cons 3/4 (+ b1 dotted-minim)) + (cons 1/2 (+ b1 minim)) + (cons 1/4 (+ b1 crotchet)))) + (unless (> (aref *results* semi dotted-minim minim crotchet 0) + 0) + (multiple-value-bind (dull score total) + (test-paths) + (declare (ignore dull)) + (setf (aref *results* semi dotted-minim minim crotchet 0) + (/ score total)))) + (unless (> (aref *results* semi dotted-minim minim crotchet 1) + 0) + (multiple-value-bind (dull score total) + (test-paths-and-chords) + (declare (ignore dull)) + (setf (aref *results* semi dotted-minim minim crotchet 1) + (/ score total)))) + (unless (> (aref *results* semi dotted-minim minim crotchet 2) + 0) + (multiple-value-bind (dull score total) + (test-harmonies-by-window) + (declare (ignore dull)) + (setf (aref *results* semi dotted-minim minim crotchet 2) + (/ score total)))) + (unless (> (aref *results* semi dotted-minim minim crotchet 3) + 0) + (multiple-value-bind (dull score total) + (test-harmonies-by-beat) + (declare (ignore dull)) + (setf (aref *results* semi dotted-minim minim crotchet 3) + (/ score total)))))))))) + *results*)) + +(defparameter *results* (make-array '(10 10 10 10 4) :element-type 'ratio)) + +(defun explore-parameters-to-file (pathname &key (alpha-scale '(0.4 3)) (beta '(4 14))) + (with-open-file (stream pathname :direction :output :if-exists :supersede) + (let* ((original-alpha *alpha*) + (original-betas *betas*)) + (loop for i from (first alpha-scale) to (second alpha-scale) by 0.3 + do (progn + (setf *alpha* (map 'vector + #'(lambda (x) + (* x i)) + original-alpha)) + (print *alpha*) + (explore-betas-to-stream beta stream))) + (setf *alpha* original-alpha + *betas* original-betas)))) + +(defun explore-betas-to-stream (beta stream) + (let* ((b1 (first beta)) + (bn (second beta)) + (n (- bn b1)) (tb #\tab)) + (loop for semi from 0 to (1- n) by 2 + do (progn + (format *standard-output* "|~D |" (+ semi b1)) + (finish-output) + (loop for dotted-minim from 0 to (1- n) by 2 + do (loop for minim from 0 to (1- n) by 2 + do (loop for crotchet from 0 to (1- n) by 2 + do (progn + (format stream "~D~C~D~C~D~C~D~C~D~C" + (aref *alpha* 0) tb + (+ b1 semi) tb + (+ b1 dotted-minim) tb + (+ b1 minim) tb + (+ b1 crotchet) tb) + (setf *harmonic-evaluation-period-cache* nil + *betas* (list (cons 1 (+ b1 semi)) + (cons 3/4 (+ b1 dotted-minim)) + (cons 1/2 (+ b1 minim)) + (cons 1/4 (+ b1 crotchet)))) + (multiple-value-bind (dull score total) + (test-paths) + (declare (ignore dull)) + (format stream "~D~C" (/ score total) tb)) + (multiple-value-bind (dull score total) + (test-paths-and-chords) + (declare (ignore dull)) + (format stream "~D~C" (/ score total) tb)) + (multiple-value-bind (dull score total) + (test-harmonies-by-window) + (declare (ignore dull)) + (format stream "~D~C" (/ score total) tb)) + (multiple-value-bind (dull score total) + (test-harmonies-by-beat) + (declare (ignore dull)) + (format stream "~D~C~%" (/ score total) tb)) + (finish-output stream))))))))) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/harmony/gamma.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,233 @@ +;; CSR's gamma functions. I don't really know what's happening here, +;; but it seems to work... Called from within harmonic_analysis.lisp + +(in-package #:amuse-harmony) + +(let ((p (make-array + 9 :element-type 'double-float + :initial-contents ; cf. Lanczos' Approximation on Wikipedia. + '(0.99999999999980993d0 676.5203681218851d0 -1259.1392167224028d0 + 771.32342877765313d0 -176.61502916214059d0 12.507343278686905d0 + -0.13857109526572012d0 9.9843695780195716d-6 + 1.5056327351493116d-7))) + (c (make-array 8 :element-type 'double-float + :initial-contents + (mapcar (lambda (x) (float x 1d0)) + '(1/12 -1/360 1/1260 -1/1680 1/1188 -691/360360 + 1/156 -3617/122400))))) + (defun gamma/posreal (x) + (declare (type (double-float (0d0)) x)) + (locally + (declare (optimize speed)) + (labels ((corr (x) + (declare (type double-float x)) + (let ((y (/ 1.0 (* x x)))) + (do* ((i 7 (1- i)) + (r (aref c i) (+ (* r y) (aref c i)))) + ((<= i 0) (the (double-float (0d0)) (exp (/ r x)))) + (declare (type double-float r))))) + (lngamma/lanczos (x) + (declare (type (double-float 0.5d0) x)) + (let ((x (1- x))) + (do* ((i 0 (1+ i)) + (ag (aref p 0) (+ ag (/ (aref p i) (+ x i))))) + ((>= i 8) + (let ((term1 (* (+ x 0.5d0) (log (/ (+ x 7.5d0) #.(exp 1d0))))) + (term2 (+ #.(log (sqrt (* 2 pi))) (log ag)))) + (+ term1 (- term2 7.0d0)))) + (declare (type (double-float (0d0)) ag))))) + (gamma/xgthalf (x) + (declare (type (double-float 0.5d0) x)) + (cond + ((= x 0.5d0) 1.77245385090551602729817d0) + ((< x 5d0) (exp (lngamma/lanczos x))) + ;; the GNU scientific library suggests a third branch + ;; for x < 10, but in fact for our purposes the + ;; errors are under control. + (t + (let* ((p (expt x (* x 0.5))) + (e (exp (- x))) + (q (* (* p e) p)) + (pre (* #.(sqrt 2) #.(sqrt pi) q (/ (sqrt x))))) + (* pre (corr x))))))) + (if (> x 0.5d0) + (gamma/xgthalf x) + (let ((g (gamma/xgthalf (- 1d0 x)))) + (declare (type double-float g)) + (/ pi g (sin (* pi x))))))))) + +(defun beta (alpha) + (let ((sum 0) + (product 1)) + (dotimes (i (length alpha) (/ product (gamma/posreal (float sum 1d0)))) + (let ((alpha_i (aref alpha i))) + (setq product (* product (gamma/posreal (float alpha_i 1d0)))) + (incf sum alpha_i))))) + +;; this is p(d|c_i), where the chord model is represented as a +;; Dirichlet distribution with parameters alpha_i +#+nil +(defun likelihood (observations alpha) + ;; observations is a 12d vector summing to 1, alpha summing to 3 + (assert (< (abs (1- (loop for d across observations sum d))) + (* single-float-epsilon 12))) + (let ((pairs (loop for alpha_i across alpha + for d_i across observations + if (zerop alpha_i) + do (unless (zerop d_i) + (return-from likelihood 0.0d0)) + else + collect (cons alpha_i d_i)))) + ;; now all the CARs of pairs have strictly positive alpha_i + ;; values, and the d_i are the corresponding observations. + (let ((alpha_prime (map 'simple-vector #'car pairs))) + (* (/ (beta alpha_prime)) + (reduce #'* pairs :key (lambda (pair) + (expt (cdr pair) (1- (car pair))))))))) + + +(defun likelihood (observations alpha &optional (power 1)) + (assert (< (abs (1- (loop for d across observations sum d))) + (* single-float-epsilon 12))) + (let* ((alpha_0 (loop for a across alpha sum a)) + (alpha_0+1 (1+ alpha_0)) + (alpha_00+1 (* alpha_0+1 power)) + (alpha_00 (- alpha_00+1 1)) + (alpha (map 'simple-vector + (lambda (x) (* x (/ alpha_00 alpha_0))) + alpha)) + (quantum (expt 0.0005 power))) ; sake of argument + (let ((pairs + (loop for d_i across observations + for alpha_i across alpha + if (zerop alpha_i) + do (unless (zerop d_i) + (return-from likelihood 0.0d0)) + else collect (cons d_i alpha_i)))) + (assert (= (length pairs) 12)) + (let ((alpha_prime (map 'simple-vector #'cdr pairs))) + (* (/ (beta alpha_prime)) (/ quantum) + (reduce #'* pairs :key + (lambda (pair) + (if (zerop (car pair)) + (* (/ (cdr pair)) (expt quantum (cdr pair))) + (* quantum (expt (car pair) (1- (cdr + pair)))))))))))) + +(defun 4ple-likelihood (pitch-classes chord-probabilities intervals level sum) + (let ((observations)) + (dolist (interval intervals) + (push (aref pitch-classes interval) observations) + (decf sum (aref pitch-classes interval))) + (push sum observations) + (likelihood/fourwise (make-array (length observations) :initial-contents (reverse observations)) + chord-probabilities level))) + +(let ((alpha_0s '((1 . 12) (1/2 . 9) (1/4 . 30)))) + (defun likelihood/fourwise (observations alpha &optional (power 1)) + (assert (< (abs (1- (loop for d across observations sum d))) + (* single-float-epsilon 4))) + (let* ((alpha_0 (loop for a across alpha sum a)) + (alpha (map 'simple-vector + (lambda (x) (* x (/ (cdr (assoc power alpha_0s)) + alpha_0))) + alpha)) + (quantum (expt 0.0005 power))) ; sake of argument + (let ((pairs + (loop for d_i across observations + for alpha_i across alpha + if (zerop alpha_i) + do (unless (zerop d_i) + (return-from likelihood/fourwise 0.0d0)) + else collect (cons d_i alpha_i)))) + (assert (= (length pairs) 4)) + (let ((alpha_prime (map 'simple-vector #'cdr pairs))) + (* (/ (beta alpha_prime)) (/ quantum) + (reduce #'* pairs :key + (lambda (pair) + (if (zerop (car pair)) + (* (/ (cdr pair)) (expt quantum (cdr pair))) + (* quantum (expt (car pair) (1- (cdr pair))))))))))))) + +(defparameter *alpha-scale* 1) +(defparameter *beta-scale* 1) +(defparameter *alpha* #(2.925 1.95 1.625)) +(defparameter *beta* #(0.87 4.46)) +(defparameter *minimal-betas* '((1 . 8.75) (1/2 . 3) (1/4 . 2.5))) +(defparameter *full-betas-1* '((1 . 14) (1/2 . 6) (1/4 . 8))) ;; gets 144|51 +(defparameter *full-betas* '((1 . 14) (3/4 . 6) (1/2 . 6) (1/4 . 8))) ;; guess... +(defparameter *betas* *full-betas*) + +(defun 3ple-likelihood (pitch-classes chord-probabilities non-chord intervals level sum + &optional (alpha *alpha*) (beta *beta*)) + (declare (ignore chord-probabilities non-chord)) + (let ((observations)) + (dolist (interval intervals) + (push (aref pitch-classes interval) observations) + (decf sum (aref pitch-classes interval))) + (push sum observations) + (when (= sum 1) ;; So there are no chord notes at all... ?? move to likelihood function + (return-from 3ple-likelihood 0)) + (likelihood/threeplusonewise (make-array (length observations) :initial-contents (reverse observations)) + alpha beta + level) + #+nil (likelihood/threeplusonewise (make-array (length observations) :initial-contents (reverse observations)) + (map 'vector #'(lambda (x) (* x *alpha-scale*)) + #(2.925 1.95 1.625)) + #+nil (map 'vector #'(lambda (x) (* x *beta-scale*)) + #(0.87 4.46)) + (make-array 2 :initial-contents (list 0.87 *beta-scale*)) + level))) + +;; #(0.87 4.46) worked + +;;; observations is a non-relativistic four-vector of proportions: +;;; #(tonic mediant dominant other). +;;; +;;; alpha is a three-vector of Dirichlet parameters for proportions of +;;; tonic, mediant, dominant of the total chord notes. +;;; +;;; beta is a two-vector of Dirichlet parameters for proportions of +;;; non-chord vs chord notes. +;;; +;;; suggested values: +;;; alpha: #(2.925 1.95 1.625) ; (tonic, mediant, dominant) +;;; beta: #(0.44 1.76) ; (non-chord, chord) +;;; +;;; model: +;;; p(tmdo|c) = p(o|c)p(tmd|oc) +;;; where +;;; p(o|c) ~ Beta(0.44,1.76) (i.e. p({o,o'}|c) ~ Dir({0.44,1.76}) +;;; and +;;; p(tmd|oc) ~ (1-o) Dir({2.925,1.95,1.625}) +(defun likelihood/threeplusonewise + (observations alpha beta &optional (power 1)) + (assert (< (abs (1- (loop for d across observations sum d))) + (* single-float-epsilon 4))) + (let* ((quantum (expt 0.00000005 power))) ; sake of argument + (let* ((o (aref observations 3)) + (pairs + (loop repeat 3 + for d_i across observations + for alpha_i across alpha + if (zerop alpha_i) + do (unless (zerop d_i) + (return-from likelihood/threeplusonewise 0.0d0)) + else collect (cons (/ d_i (- 1 o)) alpha_i)))) + (assert (= (length pairs) 3)) + (assert (< (abs (1- (loop for (d) in pairs sum d))) (* + single-float-epsilon 3))) + (flet ((key (pair) + (if (zerop (car pair)) + (* (/ (cdr pair)) (expt quantum (cdr pair))) + (* quantum (expt (car pair) (1- (cdr pair))))))) + (let ((alpha_prime (map 'simple-vector #'cdr pairs))) + (* + ;; p(o|c) + (/ (beta beta)) (/ quantum) + (reduce #'* (list (cons o (aref beta 0)) + (cons (- 1 o) (aref beta 1))) + :key #'key) + ;; p(tmd|oc) + (/ (beta alpha_prime)) (/ quantum) + (reduce #'* pairs :key #'key)))))))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/harmony/methods.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,8 @@ +(in-package #:amuse-harmony) + +(defgeneric get-ground-truth-periods (anchored-period composition) + (:method (ap c) (declare (ignore ap c)) nil)) + +(defgeneric get-ground-truth-chord (ground-truth-period composition) + (:method (gtp c) (declare (ignore gtp c)) nil)) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/harmony/package.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,10 @@ +(cl:defpackage #:amuse-harmony + (:use #:common-lisp #:amuse #:amuse-utils) + (:export #:chord + #:chordset + #:likelihood + #:get-chord-likelihoods-for-model + #:chromatic-rotate + #:chord-labels + #:best-level + ))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/midi-output.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,155 @@ +;; Make midifiles from basic amuse objects methods here can be +;; overridden for more specific types +;; + +;; FIXME: Need to push some structures from geerdes to make this work. + +(in-package #:amuse-utils) + +(defgeneric play (music) + (:method (m) (play-midifile (make-midi m)))) +(defmethod play ((music composition)) + (play-midifile (make-midi music))) + +(defun play-midifile (midifile) + ;; coremidi is easy as an alternative, but we'll probably want midi + ;; file export anyway, so it makes some sense to focus our efforts + ;; on this first. That said, is there a CoreAudio midi file player + ;; routine? + (midi:write-midi-file midifile "tmp.mid") + #+darwin + (when (sb-impl::find-executable-in-search-path "open") + (asdf:run-shell-command "open tmp.mid") + (return-from play-midifile T)) + (when (sb-impl::find-executable-in-search-path "timidity") + (asdf:run-shell-command "timidity tmp.mid") + (return-from play-midifile T))) + +(defgeneric make-midi (sequence)) +(defmethod make-midi ((sequence sequence)) + ;; Make a midifile object. Collects global midi messages (that + ;; require a sequence) and event-based messages (that don't). + ;; FIXME: Something about this strikes me as very stupid. Must + ;; revisit + ;; FIXME: Only making type 0. Is this a problem? + (let* ((events (event-sequence-messages sequence)) + (globals (global-messages sequence)) + (patches (patch-messages sequence))) + (make-midifile-from-messages (nconc events globals patches) + :type 0))) + +(defun make-midifile-from-messages (events &key (type 0)) + ;; FIXME: clearly broken if type 1 + ;; First have to get rid of all fractional times and choose a + ;; timebase + (let* ((timebase (apply #'lcm (mapcar #'(lambda (x) + (denominator + (midi:message-time x))) + events)))) + (when (< timebase 4) + (setf timebase (* 4 timebase))) + (loop for e in events + do (setf (midi:message-time e) (* timebase + (midi:message-time e)))) + (make-instance 'midi:midifile + :format type + :division timebase + :tracks (list (sort-midi-messages-for-output events))))) + +(defun sort-midi-messages-for-output (messages) + (sort messages #'(lambda (x y) (or (< (midi:message-time x) + (midi:message-time y)) + (and (= (midi:message-time x) + (midi:message-time y)) + (> (midi::message-status x) + (midi::message-status y))))))) + +(defun event-sequence-messages (sequence) + (let ((midinotes)) + (sequence:dosequence (event sequence midinotes) + (let ((messages (event-messages event))) + (dolist (message messages) + (push message midinotes)))))) + +(defun patch-messages (sequence) + (let ((patches (make-array 16 :initial-element nil)) + (patch-list) + (channel 0) + (patch 0)) + (sequence:dosequence (event sequence patch-list) + (setf channel (get-channel-for-midi event) + patch (get-patch-for-midi event)) + (when (or (not (aref patches channel)) + (not (= (aref patches channel) + patch))) + (push (make-instance 'midi:program-change-message + :program patch + :time (timepoint event) + :status (+ channel 192)) + patch-list) + (setf (aref patches channel) patch))))) + + +(defgeneric global-messages (sequence) + (:method (s) (declare (ignore s)) nil)) +(defmethod global-messages ((sequence composition)) + ;; FIXME: missing plenty of other messages + ;; FIXME: messy + (let ((tempi (tempi sequence)) + (temp) + (time-sigs (time-signatures sequence)) + (events)) + (dolist (tempo tempi) + (setf temp (tempo-message tempo)) + (when temp + (push temp events))) + (dolist (time-sig time-sigs events) + (setf temp (time-sig-message time-sig)) + (when temp + (push temp events))))) + +(defgeneric tempo-message (tempo) + (:method (tp) + (progn + (let ((speed (make-instance 'midi:tempo-message + :time (timepoint tp) + :status 255))) + (setf (slot-value speed 'midi::tempo) (microseconds-per-crotchet tp)) + speed)))) + +(defgeneric time-sig-message (time-sig) + (:method (ts) (declare (ignore ts)) nil)) + +(defgeneric event-messages (event) + (:method (e) (declare (ignore e)) nil)) +(defmethod event-messages ((event pitched-event)) + (list (make-instance 'midi:note-on-message + :status (+ (get-channel-for-midi event) 144) + :key (midi-pitch-number event) + :velocity (get-velocity-for-midi event) + :time (timepoint event)) + (make-instance 'midi:note-off-message + :status (+ (get-channel-for-midi event) 128) + :key (midi-pitch-number event) + :velocity (get-velocity-for-midi event) + :time (timepoint (cut-off event))))) + +(defmethod event-messages ((event percussive-event)) + (list (make-instance 'midi:note-on-message + :status 153 + :key (get-pitch-for-midi event) + :velocity (get-velocity-for-midi event) + :time (timepoint event)) + (make-instance 'midi:note-off-message + :status 137 + :key (get-pitch-for-midi event) + :velocity (get-velocity-for-midi event) + :time (timepoint (cut-off event))))) + +(defgeneric get-pitch-for-midi (event)) +(defgeneric get-velocity-for-midi (event) + (:method (e) (declare (ignore e)) 100)) +(defgeneric get-patch-for-midi (event) + (:method (e) (declare (ignore e)) 0)) +(defgeneric get-channel-for-midi (event) + (:method (e) (declare (ignore e)) 0))
--- a/utils/package.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/utils/package.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -1,3 +1,23 @@ (cl:defpackage #:amuse-utils - (:use #:common-lisp) - (:export)) + (:use #:common-lisp #:amuse #:midi) + (:export #:pitchedp + #:unpitchedp + #:midi-pitch-distribution + #:pitch-class-distribution + #:normalised-midi-pitch-distribution + #:normalised-pitch-class-distribution + #:normalise-vector + #:bar-number + #:bass-note + #:crotchets-in-a-bar + #:sounding-events + #:play + #:make-midi + #:global-messages + #:tempo-message + #:event-messages + #:get-patch-for-midi + #:get-channel-for-midi + #:get-pitch-for-midi + #:get-velocity-for-midi + ))
--- a/utils/utils.lisp Mon Dec 18 13:23:31 2006 +0000 +++ b/utils/utils.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -2,3 +2,79 @@ (cl:in-package #:amuse-utils) +;; Booleans (for filters) +(defgeneric pitchedp (event) + (:method (e) (declare (ignore e)) nil)) +(defmethod pitchedp ((event amuse:pitched-event)) + T) +(defgeneric unpitchedp (event) + (:method (e) (not (pitchedp e)))) + +;; Rhythm methods +(defgeneric crotchets-in-a-bar (time-signature)) +(defmethod crotchets-in-a-bar ((time-signature basic-time-signature)) + (let ((num (time-signature-numerator time-signature)) + (den (time-signature-denominator time-signature))) + (* num (/ 4 den)))) + +;; Pitch methods + +(defgeneric sounding-events (anchored-period sequence)) +(defmethod sounding-events ((anchored-period anchored-period) + (sequence composition)) + (let ((sounding)) + (sequence:dosequence (event sequence (reverse sounding)) + (cond + ((time>= event (cut-off anchored-period)) + (return-from sounding-events (reverse sounding))) + ((period-intersection anchored-period event) + (push event sounding)))))) + +(defgeneric midi-pitch-distribution (anchored-period composition)) +(defmethod midi-pitch-distribution ((anchored-period anchored-period) + composition) + (let ((pitches (make-array 128 :initial-element 0))) + (dolist (event (remove-if #'unpitchedp (sounding-events anchored-period composition)) pitches) + (let ((overlap (period-intersection anchored-period event))) + (if overlap + (incf (aref pitches (midi-pitch-number event)) + (duration overlap)) + (if (= (duration event) 0) + (format t "~%Note ~D beats in has no duration" (timepoint event)) + (error "This function has gone wrong - looking for overlaps that don't exist"))))))) + +(defgeneric pitch-class-distribution (anchored-period composition)) +(defmethod pitch-class-distribution ((anchored-period anchored-period) + composition) + (let ((pitches (make-array 12 :initial-element 0))) + (dolist (event (remove-if #'unpitchedp (sounding-events anchored-period composition)) pitches) + (let ((overlap (period-intersection anchored-period event))) + (if overlap + (incf (aref pitches (pitch-class event)) + (duration overlap)) + (if (= (duration event) 0) + (format t "~%Note ~D beats in has no duration" (timepoint event)) + (error "This function has gone wrong - looking for overlaps that don't exist"))))))) + +(defun normalised-midi-pitch-distribution (object1 object2) + (normalise-vector (midi-pitch-distribution object1 object2))) +(defun normalised-pitch-class-distribution (object1 object2) + (normalise-vector (pitch-class-distribution object1 object2))) +(defun normalise-vector (vector &optional (target-sum 1)) + (let ((total (loop for i from 0 to (1- (length vector)) + sum (aref vector i)))) + (cond + ((= total target-sum) + vector) + ((= total 0) + (make-array (length vector) + :initial-element (/ target-sum (length vector)))) + (t + (map 'vector #'(lambda (x) (* x (/ target-sum total))) vector))))) + +;; Not as simple as it seems - have to take into account numbering +;; practices and leading silences in representations where bar number +;; isn't part of the explicit structure. +(defgeneric bar-number (moment composition)) + +(defgeneric bass-note (anchored-period composition)) \ No newline at end of file