view utils/utils.lisp @ 139:ebfe054eea1c

Bug fixes from name changes darcs-hash:20070918114952-f76cc-feb59725a2f67edd6242947a5c2311d0a724cc43.gz
author David Lewis <d.lewis@gold.ac.uk>
date Tue, 18 Sep 2007 12:49:52 +0100
parents fd85f52d9f9d
children f4e7892c017f
line wrap: on
line source
;;; General purpose utilities 

(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)
  (:documentation "Convenient function for finding the number of
  crotchet beats in a bar based on the provided
  time-signature. It should be borne in mind that this needn't be
  an integer - a time signature of 3/8, for example, should yield
  an answer of 3/2"))
(defmethod crotchets-in-a-bar ((time-signature standard-time-signature))
  (let ((num (time-signature-numerator time-signature))
	(den (time-signature-denominator time-signature)))
    (* num (/ 4 den))))

(defgeneric beats-to-seconds (object1 object2))
(defmethod beats-to-seconds ((object1 standard-anchored-period)
			     (object2 constituent))
  (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 (if (disjoint tempo object1)
		  0
		  (* (/ (duration (period-intersection tempo object1))
			(duration (crotchet object2)))
		     (amuse:microseconds-per-crotchet tempo)))))))
(defmethod beats-to-seconds ((object1 standard-moment)
			     (object2 constituent))
  (beats-to-seconds (time- (onset object1)
			   (make-standard-moment 0))
		    object2))
					  
;; 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)
  (:documentation "Returns the bar number of moment in
  composition. N.B. Although this will be a designated value in
  some, particularly score-based, implementations, it will be a
  derived value in others, particularly midi, where it may be
  necessary to take into account numbering practices and leading
  silences."))
(defgeneric bar-onset (bar-number composition)
  (:documentation "Returns a moment for the beginning of the bar
  with the given bar-number. Cautions about bar numbering as
  given in the bar-number documentation apply here also."))

(defgeneric bass-note (anchored-period composition))


(defun levenshtein-distance (s1 s2 &key (insertion-cost 1)
			  (insertion-function) (deletion-cost 1)
			  (deletion-function) (substitution-cost 1)
			  (substitution-test #'equal) (substitution-function))
  ;; This is an implementation of the Levenshtein distance measure
  ;; based on the cliki asdf package, itself based on the wikipedia
  ;; scheme example of the same algorithm. This version is generalised
  ;; such that operations costs may take constant or calculated
  ;; values. If insertion-function, deletion-function or
  ;; substitution-test are specified, the applicable cost values are
  ;; ignored and the function output is used instead.
  (let* ((width (1+ (length s1)))
	 (height (1+ (length s2)))
	 (d (make-array (list height width))))
    (dotimes (x width)
      (setf (aref d 0 x) (* x deletion-cost)))
    (dotimes (y height)
      (setf (aref d y 0) (* y insertion-cost)))
    (dotimes (x (length s1))
      (dotimes (y (length s2))
	(setf (aref d (1+ y) (1+ x))
	      (min (+ (if insertion-function
			  (apply insertion-function (elt s1 x))
			  insertion-cost)
		      (aref d y (1+ x)))
		   (+ (if deletion-function
			  (apply deletion-function (elt s2 y))
			  deletion-cost)
		      (aref d (1+ y) x))
		   (+ (aref d y x)
		      (if substitution-function
			  (apply substitution-function (list (elt s1 x) (elt s2 y)))
			  (if (apply substitution-test (list (elt s1 x) (elt s2 y)))
			      0
			      substitution-cost)))))))
    (aref d (1- height) (1- width))))

;;;;;;;;;;;;;;;;;;;;;;
;;
;; More experimental (from amuse-geerdes)
;;
;; Monody functions

(defun monodificate (composition)
  (let ((events-bags) (latest-cut-off))
  ;; - Filter out very short notes (<50ms) 
  ;; - If there are notes with the same onset time or a large
  ;;   proportion (e.g. >25%) of the notes in the segment have
  ;;   overlapping durations (of >75%), do for every simultaneous or
  ;;   overlapping pair of notes
  ;; -- if one note is louder than the other note (e.g. quieter note
  ;;    <75% of louder one) select it as melody note
  ;; -- else select note with higher pitch
  ;; [FIXME: I'm ignoring overlaps for the time being]
  ;; - For non-simultaneous notes with little overlap, set note ends
  ;;   to beginning of of onset of next (overlapping) note.

  ;; STEP 1: 
  ;; `Filter out very short notes (<50ms)' and find `segments' for
  ;; further filtering.
    (sequence::dosequence (event composition)
      (when (> (beats-to-seconds event composition)
	       1/20)
	(if (or (not latest-cut-off)
		(time> (onset event) latest-cut-off))
	    (push (list event) events-bags)
	    (push event (car events-bags)))
	(when (or (not latest-cut-off)
		  (time> (cut-off event) latest-cut-off))
	  (setf latest-cut-off (cut-off event)))))
    ;; Now check each segment for overlaps and
    ;; simultanaieties. N.B. this is a reverse list of reversed
    ;; lists.
    (let ((adjusted-bags))
      (dolist (events-bag events-bags)
	(setf events-bag (reverse events-bag))
	(let ((polyphonic-p (check-events-bag-for-polyphony events-bag)))
	  (cond
	    (polyphonic-p
	     (push (resolve-polyphony events-bag composition) adjusted-bags))
	    (t
	     (if (cdr events-bag)		   
		 (push (adjust-durations events-bag) adjusted-bags)
		 (push events-bag adjusted-bags))))))
      (apply #'nconc adjusted-bags))))

(defun resolve-polyphony (event-list composition)
  (do ((i 0 (1+ i)))
      ((>= i (length event-list)) event-list)
    (let ((event (nth i event-list)))
      (do ((j (1+ i) (1+ j)))
	  ((or (>= j (length event-list))
	       (time>= (onset (nth j event-list))
		       (cut-off event))))
	(let* ((event-2 (nth j event-list))
	       (inter-onset (time- (onset event-2) (onset event))))
	  (cond
	    ((and inter-onset
		  (< (* 2 (duration inter-onset))
		      (duration event))
		  (< (* 2 (duration inter-onset))
		      (duration event-2))
		  (< (beats-to-seconds inter-onset composition)
		      1/8))
	     ;; This is clearly polyphony
	     (cond
	       ((significantly-louderp event-2 event)
		;; Take event-2
		(setf event-list (remove event event-list))
		(decf i)
		(return))
	       ((significantly-louderp event event-2)
		;; Take event
		(setf event-list (remove event-2 event-list))
		(decf j))
	       ((pitch> event event-2)
		;; Take event
		(setf event-list (remove event-2 event-list))
		(decf j))
	       (t
		;; Take event-2
		(setf event-list (remove event event-list))
		(decf i)
		(return))))
	    (t
	     (cond
	       ((substantially-louderp event-2 event)
		;; Take event-2
		(setf event-list (remove event event-list))
		(decf i)
		(return))
	       ((substantially-louderp event event-2)
		;; Take event
		(setf event-list (remove event-2 event-list))
		(decf j))
	       (t
		;; Take both
		(let ((event-overlap (period-intersection event event-2)))
		  (when event-overlap
		    (setf (duration event)
			  (duration (time- event-overlap event))))))))))))))

(defgeneric significantly-louderp (event1 event2)
  ;; noticably louder
  (:method (e1 e2) (declare (ignore e1 e2)) nil))

(defgeneric substantially-louderp (event1 event2)
  ;; much louder
  (:method (e1 e2) (declare (ignore e1 e2)) nil))

(defun adjust-durations (events-list)
  (do* ((old-list events-list (cdr old-list))
	(event (first old-list) (first old-list))
	(event-2 (second old-list) (second old-list)))
       ((not event-2) events-list)
    (let ((event-overlap (period-intersection event event-2)))
      (when event-overlap
	(setf (duration event)
	      (duration (time- event-overlap event)))))))

(defun check-events-bag-for-polyphony (events-bag)
  (let ((overlaps (make-array (length events-bag) :initial-element nil)))
    (when (= (length events-bag) 1)
      ;; obviously no overlaps
      (return-from check-events-bag-for-polyphony nil))
    (unless (= (length (remove-duplicates events-bag :test #'time=))
	       (length events-bag))
      ;; Duplicated onsets
      (return-from check-events-bag-for-polyphony 'T))
    ;; Now for the main bit
    (do* ((events events-bag (cdr events))
	  (i 0 (1+ i))
	  (event (car events) (car events)))
	 ((null (cdr events)))
      (unless (and (aref overlaps i)
		   (= (aref overlaps i) 1))
	;; Would mean we already have a maximal value
	;; and don't need any more checks
	(do* ((events-2 (cdr events) (cdr events-2))
	      (j (1+ i) (1+ j))
	      (event-2 (car events-2) (car events-2)))
	     ((null events-2))
	  (when (time>= (onset event-2) (cut-off event))
	    ;; So no more overlaps
	    (return))
	  (let ((shorter (if (duration< event event-2)
			     i
			     j))
		(overlap (/ (duration (period-intersection event event-2))
			    (min (duration event) (duration event-2)))))
	    ;; only look at pairings for the shorter note. This can
	    ;; have odd side effects, but means we never
	    ;; under-represent an overlap (I think)
	    (when (>= overlap 3/4)
	      (return-from check-events-bag-for-polyphony T))
	    (when (or (not (aref overlaps shorter))
		      (>= overlap (aref overlaps shorter)))
	      (setf (aref overlaps shorter) overlap)
	      (when (and (= shorter i)
			 (= overlap 1))
		;; Maximum value - we can stop
		(return)))))))
    (let ((total 0) (overs 0))
      (loop for i from 0 to (1- (length events-bag))
	 do (when (aref overlaps i)
	      (incf total)
	      (when (>= (aref overlaps i) 1/2)
		(incf overs))))
      (if (and (> total 0)
	       (>= (/ overs total)
		   1/4))
	  T
	  nil))))

(defgeneric inter-onset-intervals (composition &key rounding-divisor))
(defmethod inter-onset-intervals ((composition composition) &key (rounding-divisor 1/4))
  ;; returns values - list inter-onset intervals in beats, modal i-o-i
  ;; and i-o-is in seconds.
  ;; ** Only makes sense for monodic music
  ;; FIXME: Should this keep in objects or am I right to make numbers
  ;; here?
  ;; FIXME: Should I (do I) filter out 0s?
  (let ((i-o-i-list) (i-o-i-secs-list) (prev)
	(hits (make-array (1+ (/ 32 rounding-divisor)))))
    (loop for event being the elements of composition
       do (progn
	    (when prev
	      (let* ((i-o-i-period (inter-onset-interval prev event))
		     (i-o-i (duration i-o-i-period))
		     (i-o-i-secs (beats-to-seconds i-o-i-period composition)))
		(when (= i-o-i-secs 0)
		  (format t "~D, ~D -- " (timepoint prev) (timepoint event)))
		(push i-o-i i-o-i-list)
		(push i-o-i-secs i-o-i-secs-list)
		(when (< i-o-i 32)
		  ;; Not really interested in very long results for the
		  ;; modal value anyway.
		  (incf (aref hits (round i-o-i rounding-divisor))))))
	    (setf prev event)))
    (let ((mode '(0 0)))
      ;; we want the position of the highest mode
      (loop for i downfrom (1- (length hits)) to 0
	 when (> (aref hits i) (car mode))
	 do (setf mode (list (aref hits i) i)))
      (values (reverse i-o-i-list)
	      (* (cadr mode) rounding-divisor)
	      (reverse i-o-i-secs-list)))))

(defun pitch-interval-list (composition)
  (let ((intervals)
	(previous-event))
    (sequence:dosequence (event composition (reverse intervals))
      (when previous-event
	(push (span (pitch- event previous-event))
	      intervals))
      (setf previous-event event))))