view utils/harmony/evaluation.lisp @ 39:262aa7a3d500

Fix (?) for vector-correlation darcs-hash:20070525165241-40ec0-3fccec4b7b3b87ed31c3ea29a872ae6b63c2cf15.gz
author d.lewis <d.lewis@gold.ac.uk>
date Fri, 25 May 2007 17:52:41 +0100
parents d1010755f507
children
line wrap: on
line source
;; 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)))))))))