changeset 202:3e7b33ae3a0d

Gsharp preview 'fixes' committer: David Lewis <d.lewis@gold.ac.uk>
author David Lewis <david@localhost.localdomain>
date Wed, 08 Sep 2010 13:06:57 +0100
parents 4e0a5c7026ca
children 23b97270de8b
files tools/gsharp-output.lisp
diffstat 1 files changed, 349 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/tools/gsharp-output.lisp	Wed Sep 08 13:06:36 2010 +0100
+++ b/tools/gsharp-output.lisp	Wed Sep 08 13:06:57 2010 +0100
@@ -65,13 +65,11 @@
 ;;;;;;;;;;;;;;;;;;;;;
 ;; Top-level methods
 
-(defparameter *foo* nil)
-
 (defun write-gsharp-eps (composition pathname)
   ;; write a score eps from a composition. Most of this can be copied
   ;; straight (this is copied already from CSR's code)
   ;; Boilerplate stuff:
-  (let* ((frame (clim:make-application-frame 'gsharp:gsharp))
+  (let* ((frame (clim:make-application-frame 'gsharp::gsharp))
          (clim:*application-frame* frame)
          (esa:*esa-instance* frame))
       (clim:adopt-frame (clim:find-frame-manager :server-path '(:null)) frame)
@@ -82,12 +80,74 @@
       ;; Refresh and process
       (setf (gsharp::modified-p (car (esa:buffers frame))) t)
       (gsharp::recompute-measures (car (esa:buffers frame)))
-      (setf *foo* (car (esa:buffers frame)))
       ;; Print
       (clim:execute-frame-command
        frame `(gsharp::com-print-buffer-to-file ,pathname))
       (car (esa:buffers frame))))
 
+(defun gsharp-change-size-but-keep-bounding-box (left-margin right-edge buffer)
+  (declare (ignorable buffer))
+  (setf gsharp-buffer::*default-left-margin* left-margin
+        (gsharp-buffer::left-margin buffer) left-margin
+        gsharp-buffer::*default-right-edge* right-edge
+        (gsharp-buffer::right-edge buffer) right-edge
+        gsharp::*scale* (/ (+ left-margin right-edge) 900)
+        gsharp::*top-margin* (/ 80 gsharp::*scale*)))
+
+(defun prepare-gsharp-for-single-system-stuff (buffer)
+  (gsharp-change-size-but-keep-bounding-box 5 1100 buffer)
+  (setf gsharp-measure::*staves-per-page* (length (gsharp::staves buffer))))
+
+(defun write-gsharp-ps-single-system (composition pathname)
+  ;; write a score eps from a composition. Most of this can be copied
+  ;; straight (this is copied already from CSR's code)
+  ;; Boilerplate stuff:
+  (let* ((frame (clim:make-application-frame 'gsharp::gsharp))
+         (clim:*application-frame* frame)
+         (esa:*esa-instance* frame))
+      (clim:adopt-frame (clim:find-frame-manager :server-path '(:null)) frame)
+      (clim:execute-frame-command frame '(gsharp::com-new-buffer))
+      ;; Now generate the buffer
+      (make-objects-for-gsharp-buffer composition (car (esa:buffers frame)))
+      ;; (fill-gsharp-buffer-with-constituent (car (esa:buffers frame)) composition)
+      ;; Refresh and process
+      (setf (gsharp::modified-p (car (esa:buffers frame))) t)
+      (gsharp::recompute-measures (car (esa:buffers frame)))
+      (prepare-gsharp-for-single-system-stuff (car (esa:buffers frame)))
+      (gsharp::recompute-measures (car (esa:buffers frame)))
+      ;; Print
+      (clim:execute-frame-command
+       frame `(gsharp::com-print-buffer-to-file ,pathname))
+      (car (esa:buffers frame))))
+
+(defun gsharp-preview (composition)
+  ;; see above for origin of this code
+  (let* ((frame (clim:make-application-frame 'gsharp::gsharp-minimal))
+         (clim:*application-frame* frame)
+         (esa:*esa-instance* frame))
+    (clim:adopt-frame (clim:find-frame-manager :server-path '(:clx)) frame)
+    (clim:execute-frame-command frame '(gsharp::com-new-buffer))
+    ;; Now generate the buffer
+    (make-objects-for-gsharp-buffer composition (car (esa:buffers frame)))
+    ;; (fill-gsharp-buffer-with-constituent (car (esa:buffers frame)) composition)
+    ;; make views, cursors, input states, etc.
+    (let ((view (make-instance 'gsharp::orchestra-view
+                               :buffer (car (esa:buffers frame))
+                               :cursor (gsharp::make-initial-cursor 
+					(car (esa:buffers frame))))))
+      (push view (gsharp::views gsharp::*application-frame*))
+      (setf (gsharp::view (car (gsharp::windows gsharp::*application-frame*))) view
+            (gsharp::input-state gsharp::*application-frame*) (gsharp::make-input-state)))
+    ;; Refresh and process
+    (setf (gsharp::modified-p (car (esa:buffers frame))) t)
+    (gsharp::recompute-measures (car (esa:buffers frame)))
+    (gsharp::update-page-numbers frame)
+    #+nil
+    (clim:redisplay-frame-panes frame)
+    (clim:run-frame-top-level frame)))
+
+(defparameter *composition-event-maps* (make-hash-table :test 'eq))
+
 ;;;;;;;;;;;;;;;;;;;;;;;;
 ;;
 ;; Big `walking through data structures' type functions
@@ -102,60 +162,9 @@
   so in most cases there will be a single staff for each layer. Events
   are added, and then staves are sorted using staff<. A proper layout
   object would be another way of doing this."
-  (let ((layer-names (make-hash-table :test #'equal))
-	(layer-events (make-hash-table))
-	(layer-staves (make-hash-table))
-	(staff-names (make-hash-table :test #'equal))
-	(staff-name)(staff)(layer-name)(layer)
-	(segment))
-    (sequence::dosequence (event composition)
-      ;; can't do percussion parts yet:
-      (when (pitchedp event)
-	(setf layer-name (gsharp-layer-string event)
-	      layer (gethash layer-name layer-names)
-	      staff-name (gsharp-staff-string event)
-	      staff (gethash staff-name staff-names))
-	(if staff
-	    ;; this looks a little cryptic, but we're keeping note of
-	    ;; whether the mean pitch is above C for clef guessing.
-	    (setf (third staff) (+ (third staff) 1)
-		  (second staff) (+ (second staff) (min (floor (midi-pitch-number event) 60) 1)))
-	    (setf staff (list (gsharp::make-fiveline-staff :name staff-name)
-			      (min (floor (midi-pitch-number event) 60)
-				   1)
-			      1)
-		  (gsharp::buffer (car staff)) buffer
-		  (gethash staff-name staff-names) staff))
-	(if layer
-	    (progn
-	      (unless (find (first staff) (gethash layer layer-staves))
-		(push (first staff) (gethash layer layer-staves)))
-	      (push event (gethash layer layer-events)))
-	    (if segment
-		(setf layer (gsharp::make-layer (list (first staff))
-						:body (gsharp::make-slice :bars nil)
-						:name layer-name
-						:segment segment)
-		      (gsharp::layers segment) (cons layer (gsharp::layers segment))
-		      (gethash layer-name layer-names) layer
-		      (gethash layer layer-staves) (list staff)
-		      (gethash layer layer-events) (list event))
-		(setf layer (gsharp::make-layer (list (first staff))
-						:body (gsharp::make-slice :bars nil)
-						:name layer-name)
-		      segment (make-instance 'gsharp::segment
-					     :buffer buffer
-					     :layers (list layer))
-		      (gsharp-buffer:segment layer) segment
-		      (gethash layer-name layer-names) layer
-		      (gethash layer layer-staves) (list (first staff))
-		      (gethash layer layer-events) (list event))))))
-    (maphash #'(lambda (key val)
-		 (declare (ignore key))
-		 (unless (>= (second val) (/ (third val) 2))
-		   (setf *foo* (first val))
-		   (setf (gsharp::clef (first val)) (gsharp::make-clef :bass))))
-	     staff-names)
+  (multiple-value-bind (layer-events layer-staves)
+      (%gather-objects-for-gsharp-output composition buffer)
+    ;; Add events/notes/clusters
     (maphash #'(lambda (key events)
 		 (add-music-to-layer key
 				     (reverse events)
@@ -166,17 +175,90 @@
 							 (declare (ignore c))
 							 (invoke-restart 'guess))))
 				       (get-applicable-key-signatures composition composition))))
-	     layer-events)
-    (setf (gsharp::segments buffer) (list segment)
-	  (gsharp::staves buffer) nil)
-    (let ((staves))
-      (maphash #'(lambda (key val)
-		   (declare (ignore key))
-		   (push (car val) staves))
+             layer-events)
+    buffer))
+
+(defun %gather-objects-for-gsharp-output (composition buffer)
+  (let ((layer-names (make-hash-table :test #'equal))
+        (layer-events (make-hash-table))
+        (layer-staves (make-hash-table))
+        (staff-names (make-hash-table :test #'equal))
+        (staff-name) (staff) (layer-name) (layer) (staves)
+        (segment))
+    (sequence::dosequence (event composition)
+      ;; can't do percussion parts yet:
+      (when (pitchedp event)
+        (setf layer-name (gsharp-layer-string event)
+              layer (gethash layer-name layer-names)
+              staff-name (gsharp-staff-string event)
+              staff (gethash staff-name staff-names))
+	(unless staff
+	  (setf staff (list (%new-gsharp-staff-for-amuse staff-name buffer)
+			    0 0)
+		(gethash staff-name staff-names) staff))
+	;; Keeping note of whether the mean pitch is above C for clef
+	;; guessing.
+	(setf (third staff) (+ (third staff) 1)
+	      (second staff) (+ (second staff)
+				(min (floor (midi-pitch-number event) 60) 1)))
+	;; Check if layer has happened before, if not make it
+	(unless layer
+	  (setf layer (%create-and-record-layer layer-name (first staff)
+					       segment buffer layer-names
+					       layer-staves layer-events)
+		segment (gsharp::segment layer)))
+	;; Associate new event with layer
+	(setf layer-events 
+	      (%add-event-to-layer-hash event (first staff)
+				       layer layer-events layer-staves))))
+    ;; Guess clefs for staves: bass if most pitches are below middle
+    ;; C, otherwise treble. (yes, I know this is stupid)
+    (maphash #'(lambda (key val)
+                 (declare (ignore key))
+                 (unless (>= (second val) (/ (third val) 2))
+                   (setf (gsharp::clef (first val)) (gsharp::make-clef :bass))))
+             staff-names)
+    ;; gather and sort staves
+    (maphash #'(lambda (key val)
+		 (declare (ignore key))
+		 (push (car val) staves))
 	     staff-names)
-      (setf staves (sort staves #'stave<))
-      (setf (gsharp::staves buffer) staves)
-      buffer)))
+    (setf staves (sort staves #'stave<)
+	  (gsharp::staves buffer) staves)
+    (values layer-events layer-staves)))
+
+(defun %new-gsharp-staff-for-amuse (staff-name buffer)
+  (let ((staff (gsharp::make-fiveline-staff :name staff-name)))
+    (setf (gsharp::buffer staff) buffer)
+    staff))
+
+(defun %add-event-to-layer-hash (event staff layer layer-events layer-staves)
+;;  (unless (find staff (gethash layer layer-staves))
+  (unless (member staff (gethash layer layer-staves))
+    (push staff (gethash layer layer-staves)))
+  (push event (gethash layer layer-events))
+  layer-events)
+
+(defun %create-and-record-layer (name staff segment buffer
+				 layer-names layer-staves layer-events)
+  ;; create fresh layer called name and add to all necessary objects
+  (let ((layer (gsharp::make-layer (list staff)
+				   :body (gsharp::make-slice :bars nil)
+				   :name name
+				   :segment segment)))
+    (if segment
+	(setf (gsharp::layers segment) 
+	      (cons layer (gsharp::layers segment)))
+	(setf segment (make-instance 'gsharp::segment
+				     :buffer buffer
+				     :layers (list layer))
+	      (gsharp::segment layer) segment
+	      (gsharp::segments buffer) (list segment)))
+    (setf (gethash name layer-names) layer
+	  (gethash layer layer-staves) nil
+	  (gethash layer layer-events) nil)
+    layer))
+
 (defgeneric stave< (staff1 staff2)
   (:method (s1 s2)
     (let* ((clefs '(:treble :bass))
@@ -229,12 +311,15 @@
 	    (setf current nil
 		  bars (cdr bars)))
 	  (push beat-time current))))))
-(defun add-music-to-layer (layer events staves composition key-sigs)
-  "Creating all the musical objects for the gsharp staves in the
-provided layer"
+
+(defgeneric add-music-to-layer (layer events staves composition key-sigs)
+  (:documentation "Creating all the musical objects for the gsharp staves in the
+provided layer"))
+;; change this into some sort of quantize-please mixin? or switch? or something
+(defmethod add-music-to-layer (layer events staves (composition amuse-midi::unquantized-composition) key-sigs)
   (let* ((bar-moments (bar-starts-2 composition))
 	 (beat-moments (or (beat-starts-2 bar-moments composition)
-			   bar-moments))
+			   (mapcar #'list bar-moments)))
 	 (body (gsharp::body layer))
 	 (bar-no 0)
 	 (ons) (position) (clusters) (bar))
@@ -242,7 +327,7 @@
     ;; on onset times - only affects midi-like data where onsets are
     ;; already rounded, but durations are not (as in TC's fantasia
     ;; midi files....)
-    (setf *rounding-factor* (max (guess-rounding-factor events)
+    (setf *rounding-factor* (max (guess-rounding-factor-smart events)
                                  1/8))
     ;; First create a list of change-points for when events are
     ;; sounding, of the format (time event event event) (time event))
@@ -259,7 +344,7 @@
     ;; rounding happens, but there is no check for bar length here or
     ;; within g-sharp, this should verify that everything makes
     ;; sense. At the moment, it just removes short rests...
-    (setf ons (check-ons ons bar-moments))
+    (setf ons (check-ons-2 ons bar-moments))
     ;; Now create the bars and the gsharp clusters
     (when key-sigs
       (dolist (staff staves)
@@ -267,7 +352,7 @@
 	      (make-gsharp-key-signature (car key-sigs) staff))))
     (do ((old-ons nil ons)
          (ons ons (cdr ons)))
-        ((null (cdr ons)))
+        ((null (car ons)))
       (when (member (caar ons) bar-moments)
         ;; We're at the beginning of a bar.
         (when bar (check-beams bar))
@@ -298,12 +383,20 @@
       ;; why I've taken as much of the metrical logic out and put it
       ;; above if there are other straightforward rules, they should,
       ;; I think go there.
-      (if (cdar ons)
-          (setf clusters (make-gsharp-clusters-with-duration (- (car (second ons))
-								(car (car ons)))))
-          (setf clusters (make-gsharp-rests-with-duration (- (car (second ons))
-							     (car (car ons)))
-							  layer)))
+      ;; NB now incorporating JF fix (not via version control)
+      (if (second ons)
+	  (if (cdar ons)
+	      (setf clusters (make-gsharp-clusters-with-duration (- (car (second ons))
+								    (car (car ons)))))
+	      (setf clusters (make-gsharp-rests-with-duration (- (car (second ons))
+								 (car (car ons)))
+							      layer)))
+	  (if (cdar ons)
+	      (setf clusters (make-gsharp-clusters-with-duration (duration (cadar ons))))
+	      (setf clusters (make-gsharp-rests-with-duration (- (timepoint 
+								  (cut-off composition))
+								 (caar ons))
+							      layer))))
       (let ((now (caar ons)) (first-p t) (pitches))
         (do ((clusters clusters (cdr clusters)))
             ((null clusters))
@@ -332,9 +425,10 @@
                                                                   (member (car pitch) (second ons)))
                                                               t
                                                               nil)
-                                               :tie-left (if first-p
-                                                             (member (car pitch) (first old-ons))
-                                                             t)))))
+                                               :tie-left (if (or first-p
+                                                                 (member (car pitch) (first old-ons)))
+                                                             t
+                                                             nil)))))
           (incf now (* (gsharp::duration (car clusters)) 4))
           (setf first-p nil)
           (incf position)))
@@ -344,6 +438,122 @@
         (setf bar-moments (cdr bar-moments)
               beat-moments (cdr beat-moments))))))
 
+(defclass amuse-gsharp-note (gsharp::note)
+  ((composition :initarg :composition
+		:accessor composition)
+   (event :initarg :event
+	  :accessor event)
+   (groups :initarg :groups
+	  :accessor groups)))
+
+(defun make-amuse-gsharp-note (event staves composition 
+			       &key tie-right tie-left groups)
+  (destructuring-bind (pitch accidental)
+      (pitch-for-gsharp event composition)
+    (let ((note (make-instance 'amuse-gsharp-note 
+			       :pitch pitch
+			       :accidentals accidental
+			       :staff (staff-for-note event staves)
+			       :tie-right tie-right
+			       :tie-left tie-left
+			       :composition composition
+			       :event event
+			       :groups groups))
+	  (event-map (get-event-map composition)))
+      (setf (gethash event event-map) note)
+      note)))
+
+(defun get-event-map (composition)
+  (unless (gethash composition *composition-event-maps*)
+    (setf (gethash composition *composition-event-maps*)
+	  (make-hash-table :test 'eq)))
+  (gethash composition *composition-event-maps*))
+
+(defun get-gsharp-note (event composition)
+  (gethash event (get-event-map composition)))
+
+(defmethod add-music-to-layer (layer events staves composition key-sigs)
+  ;; no beaming yet
+  (let* ((scale (duration (crotchet composition)))
+	 (times (loop for event in events
+		   collect (timepoint event)
+		   collect (timepoint (cut-off event))))
+	 (event-array)
+	 (bar-no 0) (slice (gsharp::body layer)) (bar)
+	 (position 0) (bar-starts) (clusters))
+    (do* ((bar-period (current-bar (make-moment 0) composition)
+		      (current-bar (cut-off bar-period) composition))
+	  (bar-start (when bar-period (timepoint bar-period))
+		     (when bar-period (timepoint bar-period))))
+	 ((time>= (cut-off bar-period) (cut-off composition))
+	  (setf bar-starts (reverse bar-starts)))
+      (push bar-start times)
+      (push bar-start bar-starts))
+    (setf times (sort (remove-duplicates times) #'<)
+	  event-array (make-array (list-length times) :initial-element nil))
+    ;; Create an array of which rhythmic clusters each event belongs
+    ;; to (notational clusters come later)
+    (loop for time in times
+       for i from 0
+       do (loop for event in events
+	     while (<= (timepoint event) time)
+	     when (> (timepoint (cut-off event)) time)
+	     do (push event (aref event-array i))))
+    (when (and key-sigs (= (timepoint (first key-sigs)) 0))
+      (mapcar #'(lambda (x) (set-staff-key-signature (first key-sigs) x))
+	      staves)
+      (setf key-sigs (cdr key-sigs)))
+    (do ((times times (cdr times))
+	 (i 0 (1+ i)))
+	((not times))
+      (when (and bar-starts (<= (first bar-starts) 
+				(first times)))
+	(setf bar (gsharp::make-melody-bar))
+	(gsharp::add-bar bar slice bar-no)
+	(incf bar-no)
+	(setf position 0)
+	(setf bar-starts (cdr bar-starts)))
+      (if (aref event-array i)
+	  (setf clusters (make-gsharp-clusters-with-exact-duration
+			  (/ (- (or (second times)
+				    (timepoint (cut-off composition)))
+				(first times))
+			     scale)))
+	  (setf clusters (make-gsharp-rests-with-exact-duration
+			  (/ (- (or (second times)
+				    (timepoint (cut-off composition)))
+				(first times))
+			     scale)
+			  layer)))
+      ;; FIXME: This has possible problem cases for key-sig changes
+      ;; mid-note and for multi-staff signatures. Fix this when the
+      ;; AMUSE representation is a bit richer.
+      (when (and key-sigs (<= (timepoint (first key-sigs))
+			      (first times)))
+	(gsharp::add-element (make-gsharp-key-signature (car key-sigs)
+							(gsharp::staff bar))
+		     bar position)
+	(incf position)
+	(setf key-sigs (cdr key-sigs)))
+      (do* ((clusters clusters (cdr clusters))
+	    (cluster (car clusters) (car clusters))
+	    (firstp t nil))
+	   ((not clusters))
+	(gsharp::add-element cluster bar position)
+	(incf position)
+	;; There will be trouble with same pitch in same layer,
+	;; but that's a gsharp bug, not an amuse one
+	(dolist (event (aref event-array i))
+	  (gsharp::add-note
+	   cluster
+	   (make-amuse-gsharp-note event staves composition 
+				   :tie-right (or (cdr clusters)
+						  (and (< (1+ i) (length event-array))
+						       (member event (aref event-array (1+ i)))))
+				   :tie-left (or (not firstp)
+						 (and (> i 0)
+						      (member event (aref event-array (1- i))))))))))))
+
 (defun staff-for-note (event staves)
   (find-if #'(lambda (x) (string= (gsharp::name x) (gsharp-staff-string event)))
 	   staves))
@@ -371,6 +581,10 @@
          (setf (gsharp::lbeams mid) (gsharp::rbeams left)))
         (t (setf (gsharp::rbeams mid) (gsharp::lbeams right)))))))
 
+(defun set-staff-key-signature (key-sig staff)
+  (setf (gsharp::keysig staff)
+	(make-gsharp-key-signature key-sig staff)))
+
 (defgeneric make-gsharp-key-signature (key-signature object))
 (defmethod make-gsharp-key-signature ((key-signature standard-key-signature) (layer gsharp-buffer::layer))
   (let ((alterations (make-array 7 :initial-element :natural))
@@ -479,6 +693,9 @@
 
 ;; Time 
 
+(defun make-gsharp-clusters-with-exact-duration (duration)
+  ;; at least for now
+  (make-gsharp-clusters-with-duration duration))
 (defun make-gsharp-clusters-with-duration (duration)
   "Returns a list of cluster(s) whose total duration is equal to
   duration (which is given in crotchets)"
@@ -489,12 +706,15 @@
                                        :rbeams (gsharp-duration-beams new-duration)
                                        :dots  (gsharp-duration-dots new-duration)))))
 
+(defun make-gsharp-rests-with-exact-duration (duration layer)
+  ;; at least for now
+  (make-gsharp-rests-with-duration duration layer))
 (defun make-gsharp-rests-with-duration (duration layer)
   "Returns a list of rest(s) whose total duration is equal to
   duration (which is given in crotchets)"
   (let ((new-durations (gsharp-durations-from-beats duration)))
     (loop for new-duration in new-durations
-       collect(gsharp::make-rest (car (gsharp::staves layer))
+       collect (gsharp::make-rest (car (gsharp::staves layer))
                                  :notehead (gsharp-duration-notehead new-duration)
                                  :lbeams (gsharp-duration-beams new-duration)
                                  :rbeams (gsharp-duration-beams new-duration)
@@ -670,15 +890,17 @@
   "For walking through an ordered event sequence and building up a
 list of changes to sounding pitches, this function takes an event and
 adds the time for which it sounds to the structure."
-  (let ((copied-data)
-        (on (* (round
+  (let* ((copied-data) (rounding (/ *rounding-factor* 2))
+	 (on (* (timepoint event)
+		(duration (crotchet event)))
+	 #+nil (* (round
                 (* (timepoint event)
                    (duration (crotchet event)))
-                *rounding-factor*) *rounding-factor*))
-        (off (* (round
-                 (* (timepoint (cut-off event))
-                    (duration (crotchet event)))
-                 *rounding-factor*) *rounding-factor*)))
+                rounding) rounding))
+	 (off (* (round
+		  (* (timepoint (cut-off event))
+		     (duration (crotchet event)))
+		  rounding) rounding)))
     (do ((data data (cdr data)))
         ((null data) (reverse (cons (list off)
                                     (cons (list on event)
@@ -725,6 +947,40 @@
             (unless (member note notes)
               (push note notes)))))))
 
+(defun check-ons-2 (ons bar-starts)
+  "looks for small rests such as might be created by midi performance
+of tenuto lines"
+  (let ((best-time) (found-bar) (new-ons))
+    (do* ((ons ons (cdr ons))
+	  (on1 (first ons) (first ons))
+	  (on2 (second ons) (second ons))
+	  (query nil nil))
+	 ((null on2) (reverse new-ons))
+      (unless found-bar
+	(cond
+	  ((member (first on1) bar-starts)
+	   (setf found-bar t
+		 best-time (first on1)))
+	  ((or (not best-time)
+	       (better-timep (first on1) best-time)) ;; this ought to know about tactus
+	   (setf best-time (first on1)))))
+      (when (>= (- (first on2) (first on1))
+		*rounding-factor*)
+	(push (cons best-time (cdr on1)) new-ons)
+	(setf best-time nil
+	      found-bar nil)))))
+       
+(defun better-timep (t1 t2)
+  (< (or (granularity t1 4)
+	 (granularity (/ (round t1 1/16) 16) 4))
+     (or (granularity t2 4)
+	 (granularity (/ (round t2 1/16) 16) 4))))
+
+(defun granularity (n &optional (max 16))
+  (loop for i from 1 to max
+     when (= (rem n (expt 2 (- i))) 0)
+       do (return-from granularity i)))
+
 #+nil
 
 (defun check-ons (ons)
@@ -773,4 +1029,5 @@
                               (denominator (* (timepoint x)
                                               (duration (crotchet x)))))
                     events)))
+
     (/ 1 (apply #'lcm times))))
\ No newline at end of file