Mercurial > hg > amuse
changeset 74:a98c2adfd420
initial segmentation idea
darcs-hash:20070713100751-f76cc-2cc54f680199ee8f8e7f620c5d46d1e4f059baec.gz
author | David Lewis <d.lewis@gold.ac.uk> |
---|---|
date | Fri, 13 Jul 2007 11:07:51 +0100 |
parents | c9b0739d8dd6 |
children | 6b2ac9eef629 |
files | amuse.asd base/generics.lisp tools/segmentation/classes.lisp tools/segmentation/generics.lisp tools/segmentation/methods.lisp tools/segmentation/package.lisp tools/segmentation/simple-example.lisp utils/utils.lisp |
diffstat | 8 files changed, 226 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/amuse.asd Tue Jul 10 11:08:56 2007 +0100 +++ b/amuse.asd Fri Jul 13 11:07:51 2007 +0100 @@ -18,10 +18,16 @@ ((:file "package") (:file "utils" :depends-on ("package")) (:file "n-grams" :depends-on ("package")))) - (:module tools + (:module tools :components ((:file "package") - (:file "midi-output" :depends-on ("package")))) + (:file "midi-output" :depends-on ("package")) + (:module segmentation + :components + ((:file "package") + (:file "classes" :depends-on ("package")) + (:file "generics" :depends-on ("package")) + (:file "methods" :depends-on ("package" "classes" "generics")))))) (:module implementations :components ((:module midi
--- a/base/generics.lisp Tue Jul 10 11:08:56 2007 +0100 +++ b/base/generics.lisp Fri Jul 13 11:07:51 2007 +0100 @@ -266,5 +266,4 @@ ;;; Dynamics ;;; Voice -;;; Boundary Strength (phrasing) - +;;; Boundary Strength (phrasing) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/segmentation/classes.lisp Fri Jul 13 11:07:51 2007 +0100 @@ -0,0 +1,18 @@ +(in-package "AMUSE-SEGMENTATION") + +(defclass segmenter () () + (:documentation "Base class for identifying segment-boundary + methods.")) + +(defclass basic-segmenter (segmenter) + ((direction :initarg :direction + :initform :after + :accessor %segmenter-direction) + (ground-truthp :initarg :ground-truthp + :initform nil + :accessor %segmenter-ground-truthp) + (method :initarg :method + :accessor %segmenter-method)) + (:documentation "Simple class for giving common segmenter + info. Method takes a moment-designator and a composition and + returns a positive number")) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/segmentation/generics.lisp Fri Jul 13 11:07:51 2007 +0100 @@ -0,0 +1,33 @@ +(in-package "AMUSE-SEGMENTATION") + +(defgeneric boundary-strength (segmenter object composition) + (:documentation "Returns a (positive?) number, with 0 + indicating no boundary. N.B. Whether this indicates a boundary + before or after moment-designator is the responsibility of the + caller to know.")) + +(defgeneric boundary-time (segmenter object) + (:documentation "Returns the moment that the value returned by + boundary-strength applies to if object is the second input to + the method. Essentially, this distinguishes between segmenters + that look to the beginning of a note and those that look at the + end.") + +(defgeneric ground-truth-segmenter-before (composition) + (:documentation "Returns the segmenter that, when put into + boundary-strength, supplies segmentation as given in a + ground truth for the composition. N.B. This needs to return one + segmenter only, so in cases of multiple ground truths, this + method must arbitrate.")) + +(defgeneric ground-truth-segmenter-after (composition) + (:documentation "Returns the segmenter that, when put into + boundary-strength-after, supplies segmentation as given in a + ground truth for the composition. N.B. This needs to return one + segmenter only, so in cases of multiple ground truths, this + method must arbitrate.")) + +(defgeneric ground-truth-segmenterp (segmenter)) ;; prob not necessary? +(defgeneric segments-beforep (segmenter)) +(defgeneric segments-afterp (segmenter) + (:method (s) (not (segments-beforep s))))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/segmentation/methods.lisp Fri Jul 13 11:07:51 2007 +0100 @@ -0,0 +1,76 @@ +(in-package "AMUSE-SEGMENTATION") + +(defmethod boundary-strength ((segmenter basic-segmenter) object composition) + (funcall (%segmenter-method segmenter) object composition)) + +(defmethod boundary-time ((segmenter basic-segmenter) (object anchored-period-designator)) + (if (segments-beforep segmenter) + (onset object) + (cut-off object))) +(defmethod boundary-time (segmenter (object moment-designator)) + object) +(defmethod ground-truth-segmenterp ((segmenter basic-segmenter)) + (%segmenter-ground-truthp segmenter)) +(defmethod segments-beforep ((segmenter basic-segmenter)) + (eq (%segmenter-direction segmenter) :before)) + +(defun make-basic-segmenter (method &key (ground-truthp nil) (direction :after)) + (make-instance 'basic-segmenter :method method :ground-truthp ground-truthp :direction direction)) + +;;;;;;;;;;;;;;;;;;;;;;; +;;; Seriously made-up stuff (= dodgy) + +(defun map-segmenters (composition &key (start-boundary-segmenter nil) (end-boundary-segmenter nil)) + (cond + ((and start-boundary-segmenter end-boundary-segmenter) + (%map-non-contiguous-segmenters composition start-boundary-segmenter end-boundary-segmenter)) + (start-boundary-segmenter + (%map-start-segmenter composition start-boundary-segmenter)) + (end-boundary-segmenter + (%map-end-segmenter composition end-boundary-segmenter)) + (t (error "Map-segmenters called with no segmenters")))) + +(defun %map-start-segmenter (composition segmenter) + (let ((segments)) + (sequence:dosequence (event composition (progn + (setf (duration (car segments)) + (duration (time- (cut-off composition) + (onset (car segments))))) + (reverse segments))) + (when (> (boundary-strength segmenter event composition) 0) + (when segments + (setf (duration (car segments)) + (duration (time- (boundary-time segmenter event) + (onset (car segments)))))) + (push (make-instance 'anchored-period + :time (timepoint (boundary-time segmenter event))) + segments))))) + +(defun %map-end-segmenter (composition segmenter) + (let ((segments (list (make-instance 'anchored-period :time (timepoint composition))))) + (sequence:dosequence (event composition (reverse (cdr segments))) + (when (> (boundary-strength segmenter event composition) 0) + (setf (duration (car segments)) + (duration (time- (boundary-time segmenter event) + (onset (car segments))))) + (push (make-instance 'anchored-period + :time (timepoint (boundary-time segmenter event))) + segments))))) + +(defun %map-non-contiguous-segmenters (composition start-boundary-segmenter end-boundary-segmenter) + ;; FIXME: crappy queue implementation + ;; FIXME: This only returns paired starts and ends. Should we + ;; process unpaired boundaries? + (let ((segments) (starts)) + (sequence:dosequence (event composition (reverse segments)) + (when (> (boundary-strength start-boundary-segmenter event composition) 0) + (setf (cdr (last starts)) + (list (boundary-time start-boundary-segmenter event)))) + (when (> (boundary-strength end-boundary-segmenter event composition) 0) + (when starts + (push (make-instance 'anchored-period + :interval (amuse::duration (time- (boundary-time end-boundary-segmenter event) + (car starts))) + :time (timepoint (pop starts))) + segments)))))) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/segmentation/package.lisp Fri Jul 13 11:07:51 2007 +0100 @@ -0,0 +1,13 @@ +(cl:defpackage #:amuse-segmentation + (:use #:common-lisp #:amuse #:amuse-utils #:midi) + (:export #:segmenter + #:basic-segmenter + #:boundary-strength + #:boundary-time + #:ground-truth-segmenter-before + #:ground-truth-segmenter-after + #:ground-truth-segmenterp + #:segments-beforep + #:segments-afterp + #:make-basic-segmenter + #:map-segmenters)) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/segmentation/simple-example.lisp Fri Jul 13 11:07:51 2007 +0100 @@ -0,0 +1,73 @@ +(in-package "AMUSE-SEGMENTATION") + +;;;;;;;;;;;;;;;;; +;; +;; Demo example: `simple segmenter' +;; +;; Simple segmenter segments a piece at long gaps between notes (where +;; `gaps' are the amount of time between successive note-onsets). +;; +;; In this implementation, the modal gap-length is calculated (the +;; highest is taken in cases of a tie) and a threshold is taken of 3.9 +;; times that length. An absolute gap of 1.5 seconds is also used if +;; beats-to-seconds is available. + +(defparameter *simple-segmenter* + (make-basic-segmenter #'simple-segmentsp :ground-truthp nil + :direction :before)) + +(defgeneric simple-segmentsp (event composition)) +(defmethod simple-segmentsp ((event moment-designator) (composition composition)) + (multiple-value-bind (i-o-i mode i-o-i-rt) + (inter-onset-intervals-for-simple-segmenter-with-cache composition) + (if (or (eq event (elt composition 0)) + (> (cdr (assoc event i-o-i)) (* mode 3.9)) + (and i-o-i-rt (> (cdr (assoc event i-o-i-rt)) + 1.5))) + 1 + 0))) + +;; helper-functions + +(defparameter *i-o-i-cache* (make-hash-table :weakness :KEY)) +(defun inter-onset-intervals-for-simple-segmenter-with-cache (composition &key (rounding-divisor 1/4)) + (unless (gethash composition *i-o-i-cache*) + (setf (gethash composition *i-o-i-cache*) + (multiple-value-list (inter-onset-intervals-for-simple-segmenter composition + :rounding-divisor rounding-divisor)))) + (values-list (gethash composition *i-o-i-cache*))) + +(defgeneric inter-onset-intervals-for-simple-segmenter (composition &key rounding-divisor) + (:documentation "Returns values: inter-onset-intervals, i-o-i + mode and real-time-i-o-is. Both sets of i-o-is are alists + of (<event> . i-o-i)")) +(defmethod inter-onset-intervals-for-simple-segmenter ((composition composition) + &key (rounding-divisor 1/4)) + ;; FIXME: (vaguely) assumes monody + (let ((i-o-i-list) (i-o-i-secs-list) (real-time t) (prev) (i-o-i-period) + (hits (make-array (1+ (/ 32 rounding-divisor)))) + (modal-value) (modal-count 0)) + (sequence:dosequence (event composition) + (when prev + (setf i-o-i-period (inter-onset-interval prev event)) + (when real-time + (setf i-o-i-secs-list + (acons event (handler-case (beats-to-seconds i-o-i-period composition) + ((or undefined-action insufficient-information) () + (setf real-time nil))) + i-o-i-secs-list))) + (setf i-o-i-list (acons event (duration i-o-i-period) i-o-i-list)) + (when (and (< (round (duration i-o-i-period) rounding-divisor) 32) + (> (round (duration i-o-i-period) rounding-divisor) 0)) + ;; larger than 32 crotchets doesn't really count as a useful + ;; i-o-i for our purposes (or am I just doing this because + ;; I'm using an array. + (incf (aref hits (round (duration i-o-i-period) rounding-divisor))))) + (setf prev event)) + (loop for i downfrom (1- (length hits)) to 0 + ;; we want the highest mode if there are several + when (> (aref hits i) modal-count) + do (setf modal-value i + modal-count (aref hits i))) + (values i-o-i-list (* modal-value rounding-divisor) (and real-time i-o-i-secs-list)))) + \ No newline at end of file
--- a/utils/utils.lisp Tue Jul 10 11:08:56 2007 +0100 +++ b/utils/utils.lisp Fri Jul 13 11:07:51 2007 +0100 @@ -20,7 +20,10 @@ (defgeneric beats-to-seconds (object1 object2)) (defmethod beats-to-seconds ((object1 anchored-period) (object2 constituent)) - (let ((tempi (get-applicable-tempi object1 object2)) + (let ((tempi (or (get-applicable-tempi object1 object2) + (signal 'undefined-action + :operation 'beats-to-seconds + :datatype 'constituent))) (s 0)) (dolist (tempo tempi (/ s 1000000)) (incf s (* (duration (period-intersection tempo object1))