view base/methods.lisp @ 226:64b795c2ff18

Fix bug in move-to-first-bar. Ignore-this: 52a48e8771d159294e9ad51cbe04034d darcs-hash:20090905200027-16a00-539b473b27ebd6b75282dac335cc8617403ed3ad.gz committer: Jamie Forth <j.forth@gold.ac.uk>
author j.forth <j.forth@gold.ac.uk>
date Thu, 24 Feb 2011 11:23:18 +0000
parents 1c58a18161b6
children 32b40c50075e
line wrap: on
line source
(cl:in-package #:amuse) 

;;; monody 

(defmethod ensure-monody ((m standard-monody))
  (let ((result t))
    (dotimes (i (1- (length m)) result)
      ;; assumes the events are time ordered which (since monody is a
      ;; subclass of time-ordered-constituent) they ought to be. 
      (let ((e1 (elt m i))
            (e2 (elt m (1+ i))))
        (unless (or (before e1 e2) (meets e1 e2))
          (setf result nil))))))

;;; diatonic pitch (represented using MIPS)

(defmethod asa-pitch-string ((mp diatonic-pitch))
  (mips:p-pn (list (%p-pc mp) (%p-pm mp))))

(defmethod diatonic-pitch-octave ((mp diatonic-pitch))
  (let* ((asa-string (asa-pitch-string mp))
         (start (position-if #'digit-char-p asa-string)))
    (values (parse-integer asa-string :start start))))

(defmethod diatonic-pitch-accidental ((mp diatonic-pitch))
  (let* ((asa-string (asa-pitch-string mp))
         (start 1)
         (end (position-if #'digit-char-p asa-string))
         (malist '((#\n . 0) (#\s . +1) (#\f . -1)))
         (multiplier (cdr (assoc (char asa-string 1) malist)))) 
    (* multiplier (- end start))))

(defmethod diatonic-pitch-name ((mp diatonic-pitch))
  (elt (asa-pitch-string mp) 0))

(defmethod middle-c ((mp diatonic-pitch))
  (make-diatonic-pitch #\C 0 4))

(defmethod midi-pitch-number ((mp diatonic-pitch))
  (+ (%p-pc mp) 21))

(defmethod octave ((mp diatonic-pitch))
  (1- (floor (midi-pitch-number mp) 12)))

(defmethod diatonic-pitch ((mp diatonic-pitch))
  mp)

(defmethod print-object ((o diatonic-pitch) stream)
  (print-unreadable-object (o stream :type t)
    (let ((asa-string (asa-pitch-string o)))
      (write asa-string :stream stream))))

(defmethod asa-interval-string ((mpi diatonic-pitch-interval))
  (mips:pi-pin (%diatonic-pitch-interval-span mpi)))

(defmethod print-object ((o diatonic-pitch-interval) stream)
  (print-unreadable-object (o stream :type t)
    (let ((asa-string (asa-interval-string o)))
      (write asa-string :stream stream))))

;;; Chromatic pitch 

(defmethod octave ((cp chromatic-pitch))
  (1- (floor (%chromatic-pitch-number cp) 12)))

(defmethod middle-c ((cp chromatic-pitch))
  (make-chromatic-pitch 60))

(defmethod chromatic-pitch ((pitch chromatic-pitch))
  pitch)

(defmethod midi-pitch-number ((pitch chromatic-pitch))
  (%chromatic-pitch-number pitch))

(defmethod midi-pitch-number ((pitch pitch))
  (%chromatic-pitch-number (chromatic-pitch pitch)))

(defmethod print-object ((o chromatic-pitch) stream)
  (print-unreadable-object (o stream :type t)
    (write (midi-pitch-number o) :stream stream)))

(defmethod print-object ((o chromatic-pitch-interval) stream)
  (print-unreadable-object (o stream :type t)
    (write (span o) :stream stream)))


(defmethod span ((pitch-interval chromatic-pitch-interval))
  (%chromatic-pitch-interval-span pitch-interval))

(defmethod duration ((period standard-period))
  (%period-interval period))

(defmethod (setf duration) ((value real) (period standard-period))
  (setf (%period-interval period) value))

(defmethod timepoint ((moment standard-moment))
  (%moment-time moment))

(defmethod (setf timepoint) ((value real) (moment standard-moment))
  (setf (%moment-time moment) value))

(defmethod cut-off ((anchored-period standard-anchored-period))
  (make-instance 'standard-moment
		 :time (+ (%moment-time anchored-period)
			  (%period-interval anchored-period))))

(defmethod print-object ((o standard-moment) stream)
  (print-unreadable-object (o stream :type t)
    (write (timepoint o) :stream stream)))

(defmethod print-object ((o standard-period) stream)
  (print-unreadable-object (o stream :type t)
    (write (duration o) :stream stream)))

(defmethod print-object ((o standard-anchored-period) stream)
  (print-unreadable-object (o stream :type t)
    (format stream "~A ~A" (timepoint o) (duration o))))

(defmethod beat-units-per-bar ((time-signature standard-time-signature))
  (%basic-time-signature-numerator time-signature))

(defmethod beat-units ((time-signature standard-time-signature))
  (%basic-time-signature-denominator time-signature))

(defmethod time-signature-equal ((ts1 standard-time-signature) 
                                 (ts2 standard-time-signature))
  (let ((n1 (time-signature-numerator ts1))
        (n2 (time-signature-numerator ts2))
        (d1 (time-signature-denominator ts1))
        (d2 (time-signature-denominator ts2)))
    (and n1 n2 (= n1 n2) 
         d1 d2 (= d1 d2))))

(defmethod print-object ((sts standard-time-signature) stream)
  (print-unreadable-object (sts stream :type t)
    (format stream "~A/~A" (beat-units-per-bar sts) (beat-units sts))))

(defmethod key-signature-sharps ((key-signature standard-key-signature))
  (%basic-key-signature-sharp-count key-signature))

(defmethod key-signature-mode ((ks midi-key-signature))
  (%midi-key-signature-mode ks))

(defmethod print-object ((mks midi-key-signature) stream)
  (print-unreadable-object (mks stream :type t)
    (format stream "~A ~A"
            (%basic-key-signature-sharp-count mks)
            (%midi-key-signature-mode mks))))

(defmethod key-signature-equal ((ks1 standard-key-signature) 
                                (ks2 standard-key-signature))
  (let ((s1 (key-signature-sharps ks1))
        (s2 (key-signature-sharps ks2)))
    (and s1 s2 (= s1 s2))))

(defmethod key-signature-equal ((ks1 midi-key-signature) 
                                (ks2 midi-key-signature))
  (let ((s1 (key-signature-sharps ks1))
        (s2 (key-signature-sharps ks2))
        (m1 (key-signature-mode   ks1))
        (m2 (key-signature-mode   ks2)))
    (and s1 s2 (= s1 s2) 
         m1 m2 (= m1 m2))))

(defmethod bpm ((tempo standard-tempo))
  (%tempo-bpm tempo))

(defmethod print-object ((tempo standard-tempo) stream)
  (print-unreadable-object (tempo stream :type t)
    (write (bpm tempo) :stream stream)))

(defmethod tempo-equal ((t1 tempo) (t2 tempo))
  (and (bpm t1) (bpm t2) (= t1 t2)))
       

;; Time protocol

(defmethod time+ ((moment standard-moment) (period standard-period))
  "Returns a <standard-moment>. Implemented as a straightforward
summation."
  (make-standard-moment (+ (timepoint moment) (duration period))))

(defmethod time+ ((period standard-period) (moment standard-moment)) ;?
  "Returns a <standard-moment>. Implemented as a straightforward
summation and defined by default as (time+ <moment> <period>)."
  (time+ moment period))

(defmethod time+ ((period1 standard-period)
		  (period2 standard-period))
  "Returns a <standard-period>. Implemented as a straightforward
summation."
  (make-standard-period (+ (duration period1)
			   (duration period2))))

(defmethod time+ ((moment1 moment) (moment2 moment))
  "Returns <condition:undefined-action>. The question makes no
sense."
  (error 'undefined-action :operation 'time+
	 :datatype (list (class-of moment1) (class-of moment2))))

(defmethod time- ((moment1 standard-moment) (moment2 standard-moment))
  "Returns <standard-anchored-period> with an onset at moment2 and
  extending to moment1"
  (make-standard-anchored-period (timepoint moment2)
				 (- (timepoint moment1)
				    (timepoint moment2))))

(defmethod time- ((moment standard-moment) (period standard-period))
  "Returns <standard-moment>. Simple subtraction."
  (make-standard-moment (- (timepoint moment)
			   (duration period))))

(defmethod time- ((period period) (moment moment)) ;?
  "Returns <condition:undefined-action>. The question makes no
sense"
  (error 'undefined-action
	 :operation 'time-
	 :datatype (list (class-of period) (class-of moment))))

(defmethod time- ((period1 standard-period) (period2 standard-period))
  "Returns <standard-period> spanning the difference of the
periods"
  (make-standard-period (- (duration period2)
			   (duration period1))))

;; these ones are less certain. I've just put them in, but think I
;; should remove them and force the user to specify what they mean
;; when they give objects that are both moments *and* periods to these
;; functions.

(defmethod time- ((object1 anchored-period) (object2 anchored-period)) ;? 
  (time- (moment object1) (moment object2)))

(defmethod time- (object1 (object2 anchored-period)) ;?
  (time- object1 (moment object2)))

(defmethod time- ((object1 anchored-period) object2) ;?
  (time- (moment object1) object2))

(defmethod time> ((object1 moment) (object2 moment))
  (> (timepoint object1) (timepoint object2)))

(defmethod time< ((object1 moment) (object2 moment))
  (< (timepoint object1) (timepoint object2)))

(defmethod time= ((object1 moment) (object2 moment))
  (= (timepoint object1) (timepoint object2)))

(defmethod duration> ((period1 standard-period) (period2 standard-period))
  (> (duration period1) (duration period2)))

(defmethod duration= ((period1 standard-period) (period2 standard-period))
  (= (duration period1) (duration period2)))

(defmethod duration* ((period1 standard-period) (object2 number))
  (make-standard-period (* (duration period1) object2)))

(defmethod duration* ((object1 number) (period standard-period))
  (duration* period object1))

(defmethod duration/ ((period standard-period) (object2 number))
  (make-standard-period (/ (duration period) object2)))

;;;; Pitch protocol

;;; Some catch-all methods for undefined operations and cases where we
;;; don't have enough information:
(macrolet ((def (name class1 class2)
             `(defmethod ,name ((object1 ,class1) (object2 ,class2))
               (error 'undefined-action :operation ',name
                :datatype (list (class-of object1) (class-of object2))))))
  (def pitch+ pitch pitch)
  (def pitch- pitch-interval pitch))

(macrolet ((def (name class1 class2)
             `(defmethod ,name ((object1 ,class1) (object2 ,class2))
               (error 'insufficient-information :operation ',name
                :datatype (list (class-of object1) (class-of object2))))))
  (def pitch+ pitch pitch-interval)
  (def pitch+ pitch-interval pitch)
  (def pitch+ pitch-interval pitch-interval)
  (def pitch- pitch pitch)
  (def pitch- pitch pitch-interval)
  (def pitch- pitch-interval pitch-interval))

;;; chromatic pitch intervals

(defmethod pitch+ ((object1 chromatic-pitch)
		   (object2 chromatic-pitch-interval))
  (make-chromatic-pitch (+ (midi-pitch-number object1) (span object2))))

(defmethod pitch+  ((object1 chromatic-pitch-interval)
		    (object2 chromatic-pitch))
  (make-chromatic-pitch (+ (span object1) (midi-pitch-number object2))))

(defmethod pitch+ ((object1 chromatic-pitch-interval)
		   (object2 chromatic-pitch-interval))
  (make-chromatic-pitch-interval (+ (span object1) (span object2))))

(defmethod pitch- ((object1 chromatic-pitch) 
                   (object2 chromatic-pitch))
  (make-chromatic-pitch-interval
   (- (midi-pitch-number object1) (midi-pitch-number object2))))

(defmethod pitch- ((object1 chromatic-pitch)
                   (object2 chromatic-pitch-interval))
  (make-chromatic-pitch (- (midi-pitch-number object1) (span object2))))

(defmethod pitch- ((object1 chromatic-pitch-interval)
		   (object2 chromatic-pitch-interval))
  (make-chromatic-pitch-interval (- (span object1) (span object2))))

(defmethod pitch> ((object1 chromatic-pitch)
		   (object2 chromatic-pitch))
  (> (midi-pitch-number object1) (midi-pitch-number object2)))

(defmethod pitch= ((object1 chromatic-pitch)
		   (object2 chromatic-pitch))
  (= (midi-pitch-number object1) (midi-pitch-number object2)))

(defmethod interval> ((object1 chromatic-pitch-interval)
                      (object2 chromatic-pitch-interval))
  (> (span object1) (span object2)))

(defmethod interval= ((object1 chromatic-pitch-interval)
                      (object2 chromatic-pitch-interval))
  (= (span object1) (span object2)))

;;; diatonic pitch intervals

(defmethod pitch+ ((object1 diatonic-pitch) (object2 diatonic-pitch-interval))
  (let* ((cp (%p-pc object1))
         (mp (%p-pm object1))
         (span (span object2))
         (cps (first span))
         (mps (second span)))
    (make-mips-pitch (+ cp cps) (+ mp mps))))

(defmethod pitch+ ((object1 diatonic-pitch-interval) (object2 diatonic-pitch))
  (let* ((cp (%p-pc object2))
         (mp (%p-pm object2))
         (span (span object1))
         (cps (first span))
         (mps (second span)))
    (make-mips-pitch (+ cp cps) (+ mp mps))))

(defmethod pitch+ ((object1 diatonic-pitch-interval)
		   (object2 diatonic-pitch-interval))
  (let* ((span1 (span object1))
         (span2 (span object2)))
    (make-mips-pitch-interval (+ (first span1) (first span2))
                              (+ (second span1) (second span2)))))

(defmethod pitch- ((object1 diatonic-pitch) (object2 diatonic-pitch))
  (let ((cp1 (%p-pc object1))
        (mp1 (%p-pm object1))
        (cp2 (%p-pc object2))
        (mp2 (%p-pm object2)))
    (make-mips-pitch-interval (- cp1 cp2) (- mp1 mp2))))

(defmethod pitch- ((object1 diatonic-pitch) (object2 diatonic-pitch-interval))
  (let* ((cp (%p-pc object1))
         (mp (%p-pm object1))
         (span (span object2))
         (cps (first span))
         (mps (second span)))
    (make-mips-pitch (- cp cps) (- mp mps))))

(defmethod pitch- ((object1 diatonic-pitch-interval)
		   (object2 diatonic-pitch-interval))
  (let ((span1 (span object1))
        (span2 (span object2)))
    (make-mips-pitch-interval (- (first span1) (first span2))
                              (- (second span1) (second span2)))))

(defmethod pitch> ((p1 diatonic-pitch) (p2 diatonic-pitch))
  (error 'undefined-action :operation 'pitch>
         :datatype (list (class-of p1) (class-of p2))))

(defmethod pitch= ((p1 diatonic-pitch) (p2 diatonic-pitch))
  (let ((c1 (%p-pc p1)) (m1 (%p-pm p1))
        (c2 (%p-pc p2)) (m2 (%p-pm p2)))
    (and c1 c2 (= c1 c2)
         m1 m2 (= m1 m2))))


;; Allen

(defmethod meets ((object1 anchored-period)
		  (object2 anchored-period))
  (or (time= (cut-off object1) object2)
      (time= (cut-off object2) object1)))

(defmethod before ((object1 anchored-period)
		   (object2 anchored-period))
  (time> object2 (cut-off object1)))

(defmethod overlaps ((object1 anchored-period)
		     (object2 anchored-period))
  ;; FIXME: Is there a tidier method?
  (or (and (time> object2 object1) ; object1 starts before object2
	   (time> (cut-off object1) object2) ; object1 ends after object2 starts
	   (time> (cut-off object2) (cut-off object1))) ; object1 ends before object2 does
      (and (time> object1 object2) ; object1 starts after object2
	   (time> (cut-off object2) object1) ; object1 starts before object2 ends
	   (time> (cut-off object1) (cut-off object2))))) ; object1 ends after object2 does

(defmethod during ((object1 anchored-period)
		   (object2 anchored-period))
  (or (and (time> object1 object2)
	   (time<= (cut-off object1) (cut-off object2)))
      (and (time>= object1 object2)
	   (time< (cut-off object1) (cut-off object2)))))

(defmethod starts ((object1 anchored-period)
		   (object2 anchored-period))
  (time= object1 object2))

(defmethod ends ((object1 anchored-period)
		 (object2 anchored-period))
  (time= (cut-off object1) (cut-off object2)))

;; ...and

(defmethod period= ((object1 anchored-period)
                    (object2 anchored-period))
  (and (time= object1 object2)
       (duration= object1 object2)))
(defmethod period= ((object1 period)
		    (object2 period))
  (duration= object1 object2))

(defmethod period-intersection ((object1 standard-anchored-period)
				(object2 standard-anchored-period))
  (cond
    ((disjoint object1 object2)
     ;; if they don't overlap, return nil, not a negative-valued
     ;; period
     nil)
    ((let* ((start (if (time> (onset object2) (onset object1))
		       (onset object2)
		       (onset object1)))
	    (duration (duration (time- (if (time> (cut-off object2) (cut-off object1))
					   (cut-off object1)
					   (cut-off object2))
				       start))))
       (make-anchored-period (timepoint start) duration)))))

;; Time constructors
(defmethod make-moment ((time-value real))
  "Returns STANDARD-MOMENT given a real"
  (make-standard-moment time-value))
(defmethod make-period ((duration-value real))
  "Returns STANDARD-PERIOD given a real"
  (make-standard-period duration-value))
(defmethod make-anchored-period ((onset-value real) (duration-value real))
  "Returns STANDARD-ANCHORED-PERIOD given a real"
  (make-standard-anchored-period onset-value duration-value))

;; Needed by some sequence functions, notably remove-if.
(defmethod sequence:make-sequence-like :around ((o standard-composition) length
						&key (initial-element nil iep)
						(initial-contents nil icp))
  "Around method for make-sequence-like, only with all slots
  preserved from the source sequence (except onset and duration,
  which are calculated afresh)."
  (declare (ignore length initial-element initial-contents iep icp))
  (let ((new-sequence (call-next-method)) (slot-name))
    ;; Get timing information
    (setf new-sequence (%recompute-standard-composition-period new-sequence))
    (dolist (slotd (sb-mop:class-slots (class-of new-sequence)) new-sequence)
      (setf slot-name (sb-mop:slot-definition-name slotd))
      (unless (or (equal slot-name '%data)
               (equal slot-name 'time)
               (equal slot-name 'interval)
               (not (slot-boundp o slot-name)))
        (setf (sb-mop:slot-value-using-class (class-of new-sequence)
					     new-sequence
					     slotd)
              (sb-mop:slot-value-using-class (class-of new-sequence)
                                             o ;; if this isn't the same, we're lost anyway
                                             slotd))))))

(defun %recompute-standard-composition-period (composition)
  "Find onset and duration times for newly-made composition object."
  (let ((start) (finish))
    (sequence:dosequence (element composition)
      ;; Actually, this next bit is pretty stupid - I know this is
      ;; ordered, so this bit could be replaced by
      ;; (setf (timepoint new-sequence)
      ;;    (timepoint (elt new-sequence 0)))
      ;; outside of the loop.
      (when (and element
                 (or (null start)
                     (< (timepoint element) start)))
        (setf start (timepoint element)))
      (when (and element
                 (or (null finish)
                     (> (timepoint (cut-off element))
                        finish)))
        (setf finish (timepoint (cut-off element)))))
    (unless start
      (setf start 0))
    (unless finish
      (setf finish 0))
    (setf (timepoint composition) start
          (duration composition) (- finish start))
    composition))


(defmethod sequence:adjust-sequence :around ((o standard-composition) length
                                             &key initial-element
                                             (initial-contents nil icp))
  (declare (ignore length o initial-element initial-contents icp))
  (%recompute-standard-composition-period (call-next-method)))

(defmethod get-constituents ((identifier composition-identifier))
  (list (get-composition identifier)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Experimental:
;;

;; Some not obviously correct implementations of the new metre
;; functions. These are no worse than we're already using (they should
;; be more or less equivalent)

(defmethod bar-period ((time-signature standard-time-signature)
                       object)
  (make-standard-period (* (duration (crotchet object))
                           (time-signature-numerator time-signature)
                           (/ 4 (time-signature-denominator time-signature)))))
        
(defmethod current-bar ((moment standard-moment) (composition composition))
  (let* ((time-sig (car (get-applicable-time-signatures
                         (make-standard-anchored-period (timepoint moment)
                                                        (duration (crotchet composition)))
                         composition)))
         (bar-duration (bar-period time-sig composition)))
    (do* ((start (onset time-sig) next-start)
          (next-start (time+ start bar-duration) (time+ start bar-duration)))
         ((time> next-start moment)
          (make-standard-anchored-period (timepoint start)
                                         (duration bar-duration))))))

(defmethod beat-period ((moment standard-moment)
                        (time-signature standard-time-signature)
                        (composition composition))
  ;; Simple example - standard-time-signature has constant tactus
  (let* ((containing-bar (current-bar moment composition))
         (beat-duration (* (duration (crotchet composition))
                           (tactus-duration time-signature)))
         (beat-period (make-standard-anchored-period (timepoint containing-bar)
                                                     beat-duration)))
    (do ()
        ((time> (cut-off beat-period) moment) beat-period)
      (setf (timepoint beat-period) (timepoint (cut-off beat-period))))))

(defmethod current-beat ((moment standard-moment) (composition composition))
  ;; Assume at most one time signature per bar (otherwise, this is hell)
  (let* ((time-sig (car (get-applicable-time-signatures (current-bar moment composition) composition))))
    (if time-sig
        (beat-period moment time-sig composition)
        ;; If no time-sig, there's no way of answering this
        ;; directly. There may be sensible defaults, but it's the job
        ;; of an implementation's author to solve that.
        (error 'insufficient-information :operation 'beat-period :datatype (class-of composition)))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Copying events in time

(defmethod move-to-first-bar ((composition composition))
  (let ((offset (floor (timepoint (elt composition 0)))))
    (loop
       for event in (%list-slot-sequence-data composition)
       do (setf event (copy-event event))
       do (setf (timepoint event)
		(- (timepoint event) offset))
       collect event into shifted-events
       finally (return shifted-events))))