]> git.donarmstrong.com Git - lilypond.git/commitdiff
Fix #129, #228, #7
authorHan-Wen Nienhuys <hanwen@xs4all.nl>
Mon, 8 Jan 2007 12:39:38 +0000 (13:39 +0100)
committerHan-Wen Nienhuys <hanwen@xs4all.nl>
Mon, 8 Jan 2007 12:39:38 +0000 (13:39 +0100)
Store length property in generated tuplet start event. Simplify
Time_scaled_music_iterator.

Conflicts:

THANKS
lily/tuplet-engraver.cc

GNUmakefile.in
buildscripts/output-distance.py
input/regression/midi-dynamics.ly
input/regression/part-combine-tuplet-end.ly [new file with mode: 0644]
input/regression/part-combine-tuplet-single.ly [new file with mode: 0644]
input/regression/part-combine-tuplet.ly [deleted file]
input/regression/quote-tuplet-end.ly [new file with mode: 0644]
lily/time-scaled-music-iterator.cc
lily/tuplet-engraver.cc
scm/output-lib.scm

index 98f9ae64277cb64c10519a0186221433053e192f..f5689137fb53a220aec6b0ef20b441d3ba4fa2fb 100644 (file)
@@ -202,15 +202,44 @@ $(config_h): config.hh.in
        @false
 
 
-test-clean:
-       $(MAKE) -C input/regression/ out=test clean
+################################################################
+# testing
+
+RESULT_DIR=$(top-build-dir)/out/test-results
+OUT_TEST=test
+
 
 test:
-       $(MAKE) -C input/regression/ out=test LILYPOND_BOOK_LILYPOND_FLAGS="--backend=eps --formats=ps $(LILYPOND_JOBS) -dinclude-eps-fonts -dgs-load-fonts --header=texidoc -I $(top-src-dir)/input/manual -ddump-profile -dcheck-internal-types -ddump-signatures -danti-alias-factor=1" LILYPOND_BOOK_VERBOSE= out-test/collated-files.html 
+       rm -f input/regression/out-$(OUT_TEST)/collated-files.html
+       @echo -en 'For tracking crashes: use\n\n\t'
+       @echo 'grep sourcefilename `grep -L systems.texi input/regression/out-test/*log|sed s/log/ly/g`'
+       @echo
+       if test -d .git  ; then \
+               echo -e 'HEAD is:\n\n\t' ; \
+               git log --max-count=1 --pretty=oneline ;\
+               echo -e '\n\n\n' ; \
+               git diff ; \
+       fi > input/regression/out-test/tree.gittxt
+       $(MAKE) -C input/regression/ out=$(OUT_TEST) LILYPOND_BOOK_LILYPOND_FLAGS="--backend=eps --formats=ps $(LILYPOND_JOBS) -dseparate-log-files -dinclude-eps-fonts -dgs-load-fonts --header=texidoc -I $(top-src-dir)/input/manual -ddump-profile -dcheck-internal-types -ddump-signatures -danti-alias-factor=1" LILYPOND_BOOK_VERBOSE= out-$(OUT_TEST)/collated-files.html
        @find input ly -name '*.ly' -print |grep -v 'out.*/' | xargs grep '\\version' -L | grep -v "standard input" |sed 's/^/**** Missing version: /g' 
 
-RESULT_DIR=$(top-build-dir)/out/test-results/
-check-test: test
+
+test-baseline: test
+       rm -rf input/regression/out-test-baseline
+       mv input/regression/out-test input/regression/out-test-baseline
+
+local-check: test
        rm -rf $(RESULT_DIR)
        mkdir -p $(RESULT_DIR)
-       $(PYTHON) $(buildscript-dir)/output-distance.py --create-images --output-dir $(RESULT_DIR) $(CHECK_SOURCE) input/regression/out-test/
+       $(PYTHON) $(buildscript-dir)/output-distance.py --create-images --output-dir $(RESULT_DIR) input/regression/out-test-baseline input/regression/out-test/
+
+
+test-redo:
+       for a in `cat $(RESULT_DIR)/changed.txt` ; do \
+               echo removing $$a* ; \
+               rm -f $$a* ;\
+       done
+       $(MAKE) check
+
+test-clean:
+       $(MAKE) -C input/regression/ out=$(OUT_TEST) clean
index ec8b5d9e10a769f2a907282d9885997a159c7902..374f7695dbf3e07e9182d8b516c00ea07d2595e8 100644 (file)
@@ -2,13 +2,12 @@
 import sys
 import optparse
 import os
+import math
 
 ## so we can call directly as buildscripts/output-distance.py
 me_path = os.path.abspath (os.path.split (sys.argv[0])[0])
 sys.path.insert (0, me_path + '/../python/')
-
-
-import safeeval
+sys.path.insert (0, me_path + '/../python/out/')
 
 
 X_AXIS = 0
@@ -19,6 +18,38 @@ OUTPUT_EXPRESSION_PENALTY = 1
 ORPHAN_GROB_PENALTY = 1
 options = None
 
+################################################################
+# system interface.
+temp_dir = None
+class TempDirectory:
+    def __init__ (self):
+        import tempfile
+        self.dir = tempfile.mkdtemp ()
+        print 'dir is', self.dir
+    def __del__ (self):
+        print 'rm -rf %s' % self.dir 
+        os.system ('rm -rf %s' % self.dir )
+    def __call__ (self):
+        return self.dir
+
+
+def get_temp_dir  ():
+    global temp_dir
+    if not temp_dir:
+        temp_dir = TempDirectory ()
+    return temp_dir ()
+
+def read_pipe (c):
+    print 'pipe' , c
+    return os.popen (c).read ()
+
+def system (c):
+    print 'system' , c
+    s = os.system (c)
+    if s :
+        raise Exception ("failed")
+    return
+
 def shorten_string (s):
     threshold = 15 
     if len (s) > 2*threshold:
@@ -34,6 +65,38 @@ def max_distance (x1, x2):
     return dist
 
 
+def compare_png_images (old, new, dest_dir):
+    def png_dims (f):
+        m = re.search ('([0-9]+) x ([0-9]+)', read_pipe ('file %s' % f))
+        
+        return tuple (map (int, m.groups ()))
+
+    dest = os.path.join (dest_dir, new.replace ('.png', '.compare.jpeg'))
+    try:
+        dims1 = png_dims (old)
+        dims2 = png_dims (new)
+    except AttributeError:
+        ## hmmm. what to do?
+        system ('touch %(dest)s' % locals ())
+        return
+    
+    dims = (min (dims1[0], dims2[0]),
+            min (dims1[1], dims2[1]))
+
+    dir = get_temp_dir ()
+    system ('convert -depth 8 -crop %dx%d+0+0 %s %s/crop1.png' % (dims + (old, dir)))
+    system ('convert -depth 8 -crop %dx%d+0+0 %s %s/crop2.png' % (dims + (new, dir)))
+
+    system ('compare -depth 8 %(dir)s/crop1.png %(dir)s/crop2.png %(dir)s/diff.png' % locals ())
+
+    system ("convert  -depth 8 %(dir)s/diff.png -blur 0x3 -negate -channel alpha,blue -type TrueColorMatte -fx 'intensity'    %(dir)s/matte.png" % locals ())
+
+    system ("composite -quality 65 %(dir)s/matte.png %(new)s %(dest)s" % locals ())
+
+
+################################################################
+# interval/bbox arithmetic.
+
 empty_interval = (INFTY, -INFTY)
 empty_bbox = (empty_interval, empty_interval)
 
@@ -274,107 +337,219 @@ def read_signature_file (name):
 
 ################################################################
 # different systems of a .ly file.
-def read_pipe (c):
-    print 'pipe' , c
-    return os.popen (c).read ()
-
-def system (c):
-    print 'system' , c
-    s = os.system (c)
-    if s :
-        raise Exception ("failed")
-    return
-
-def compare_png_images (old, new, dir):
-    def png_dims (f):
-        m = re.search ('([0-9]+) x ([0-9]+)', read_pipe ('file %s' % f))
-        
-        return tuple (map (int, m.groups ()))
-
-    dest = os.path.join (dir, new.replace ('.png', '.compare.jpeg'))
-    try:
-        dims1 = png_dims (old)
-        dims2 = png_dims (new)
-    except AttributeError:
-        ## hmmm. what to do?
-        system ('touch %(dest)s' % locals ())
-        return
-    
-    dims = (min (dims1[0], dims2[0]),
-            min (dims1[1], dims2[1]))
 
-    system ('convert -depth 8 -crop %dx%d+0+0 %s crop1.png' % (dims + (old,)))
-    system ('convert -depth 8 -crop %dx%d+0+0 %s crop2.png' % (dims + (new,)))
-
-    system ('compare -depth 8 crop1.png crop2.png diff.png')
-
-    system ("convert  -depth 8 diff.png -blur 0x3 -negate -channel alpha,blue -type TrueColorMatte -fx 'intensity'    matte.png")
-
-    system ("composite -quality 65 matte.png %(new)s %(dest)s" % locals ())
+hash_to_original_name = {}
 
 class FileLink:
+    def __init__ (self, f1, f2):
+        self._distance = None
+        self.file_names = (f1, f2)
+        
     def text_record_string (self):
         return '%-30f %-20s\n' % (self.distance (),
                                   self.name ())
-    def distance (self):
+    def calc_distance (self):
         return 0.0
 
+    def distance (self):
+        if self._distance == None:
+           self._distance = self.calc_distance ()
+
+        return self._distance
+    
+        
     def name (self):
+        base = os.path.basename (self.file_names[1])
+        base = os.path.splitext (base)[0]
+        
+        base = hash_to_original_name.get (base, base)
+        base = os.path.splitext (base)[0]
+        return base
+    
+    def extension (self):
+        return os.path.splitext (self.file_names[1])[1]
+
+    def link_files_for_html (self, dest_dir):
+        for f in self.file_names:
+            link_file (f, os.path.join (dest_dir, f))
+
+    def get_distance_details (self):
         return ''
+
+    def get_cell (self, oldnew):
+        return ''
+    
+    def get_file (self, oldnew):
+        return self.file_names[oldnew]
     
-    def link_files_for_html (self, old_dir, new_dir, dest_dir):
-        pass
+    def html_record_string (self, dest_dir):
+        self.link_files_for_html (dest_dir)
+        
+        dist = self.distance()
+        
+        details = self.get_distance_details ()
+        if details:
+            details_base = os.path.splitext (self.file_names[1])[0]
+            details_base += '.details.html'
+            fn = dest_dir + '/'  + details_base
+            open (fn, 'w').write (details)
+
+            details = '<br>(<a href="%(details_base)s">details</a>)' % locals ()
 
-    def write_html_system_details (self, dir1, dir2, dest_dir):
-        pass
+        cell1 = self.get_cell (0)
+        cell2 = self.get_cell (1)
+
+        name = self.name () + self.extension ()
+        file1 = self.get_file (0)
+        file2 = self.get_file (1)
         
-    def html_record_string (self,  old_dir, new_dir):
-        return ''
+        return '''<tr>
+<td>
+%(dist)f
+%(details)s
+</td>
+<td>%(cell1)s<br><font size=-2><a href="%(file1)s"><tt>%(name)s</tt></font></td>
+<td>%(cell2)s<br><font size=-2><a href="%(file2)s"><tt>%(name)s</tt></font></td>
+</tr>''' % locals ()
 
-class MidiFileLink (FileLink):
-    def get_midi (self, f):
+
+class FileCompareLink (FileLink):
+    def __init__ (self, f1, f2):
+        FileLink.__init__ (self, f1, f2)
+        self.contents = (self.get_content (self.file_names[0]),
+                         self.get_content (self.file_names[1]))
+        
+
+    def calc_distance (self):
+        ## todo: could use import MIDI to pinpoint
+        ## what & where changed.
+
+        if self.contents[0] == self.contents[1]:
+            return 0.0
+        else:
+            return 100.0;
+        
+    def get_content (self, f):
+        print 'reading', f
         s = open (f).read ()
-        s = re.sub ('LilyPond [0-9.]+', '', s)
         return s
+
+
+class GitFileCompareLink (FileCompareLink):
+    def get_cell (self, oldnew):
+        str = self.contents[oldnew]
+
+        # truncate long lines
+        str = '\n'.join ([l[:80] for l in str.split ('\n')])
+
+        
+        str = '<font size="-2"><pre>%s</pre></font>' % str
+        return str
     
-    def __init__ (self, f1, f2):
-        self.files = (f1, f2)
+    def calc_distance (self):
+        if self.contents[0] == self.contents[1]:
+            d = 0.0
+        else:
+            d = 1.0001 *options.threshold
 
-        s1 = self.get_midi (self.files[0])
-        s2 = self.get_midi (self.files[1])
+        return d
         
-        self.same = (s1 == s2)
+class TextFileCompareLink (FileCompareLink):
+    def calc_distance (self):
+        import difflib
+        diff = difflib.unified_diff (self.contents[0].strip().split ('\n'),
+                                     self.contents[1].strip().split ('\n'),
+                                     fromfiledate = self.file_names[0],
+                                     tofiledate = self.file_names[1]
+                                     )
         
-    def name (self):
-        name = os.path.split (self.files[0])[1]
-        name = re.sub ('.midi', '', name)
-        return name
+        self.diff_lines =  [l for l in diff]
+        self.diff_lines = self.diff_lines[2:]
         
-    def distance (self):
-        ## todo: could use import MIDI to pinpoint
-        ## what & where changed.
-        if self.same:
-            return 0
-        else:
-            return 100;
-    def html_record_string (self, d1, d2):
-        return '''<tr>
-<td>
-%f
-</td>
-<td><tt>%s</tt></td>
-<td><tt>%s</tt></td>
-</tr>''' % ((self.distance(),) + self.files)
+        return math.sqrt (float (len ([l for l in self.diff_lines if l[0] in '-+'])))
+        
+    def get_cell (self, oldnew):
+        str = ''
+        if oldnew == 1:
+            str = '\n'.join ([d.replace ('\n','') for d in self.diff_lines])
+        str = '<font size="-2"><pre>%s</pre></font>' % str
+        return str
+
+        
+class ProfileFileLink (FileCompareLink):
+    def __init__ (self, f1, f2):
+        FileCompareLink.__init__ (self, f1, f2)
+        self.results = [{}, {}]
+    
+    def get_cell (self, oldnew):
+        str = ''
+        for k in ('time', 'cells'):
+            if oldnew==0:
+                str += '%-8s: %d\n' %  (k, int (self.results[oldnew][k]))
+            else:
+                str += '%-8s: %8d (%5.3f)\n' % (k, int (self.results[oldnew][k]),
+                                         self.get_ratio (k))
+
+        return '<pre>%s</pre>' % str
+            
+    def get_ratio (self, key):
+        (v1,v2) = (self.results[0].get (key, -1),
+                   self.results[1].get (key, -1))
+
+        if v1 <= 0 or v2 <= 0:
+            return 0.0
+
+        return (v1 - v2) / float (v1+v2)
+    
+    def calc_distance (self):
+        for oldnew in (0,1):
+            def note_info (m):
+                self.results[oldnew][m.group(1)] = float (m.group (2))
+            
+            re.sub ('([a-z]+): ([-0-9.]+)\n',
+                    note_info, self.contents[oldnew])
+
+        dist = 0.0
+        factor = {'time': 1.0 ,
+                  'cells': 10.0,
+                  }
+        
+        for k in ('time', 'cells'):
+            dist += math.tan (self.get_ratio (k) /(0.5* math.pi)) * factor[k]  - 1
+
+        dist = min (dist, 100)
+        return dist
+
+    
+class MidiFileLink (TextFileCompareLink):
+    def get_content (self, oldnew):
+        import midi
+        
+        data = FileCompareLink.get_content (self, oldnew)
+        midi = midi.parse (data)
+        tracks = midi[1]
+
+        str = ''
+        j = 0
+        for t in tracks:
+            str += 'track %d' % j
+            j += 1
+
+            for e in t:
+                ev_str = repr (e)
+                if re.search ('LilyPond [0-9.]+', ev_str):
+                    continue
+                
+                str += '  ev %s\n' % `e`
+        return str
+    
+
 
 class SignatureFileLink (FileLink):
-    def __init__ (self):
-        self.original_name = ''
-        self.base_names = ('','')
+    def __init__ (self, f1, f2 ):
+        FileLink.__init__ (self, f1, f2)
         self.system_links = {}
-        self._distance = None
-    def name (self):
-        return self.original_name
-    
+
     def add_system_link (self, link, number):
         self.system_links[number] = link
 
@@ -388,12 +563,6 @@ class SignatureFileLink (FileLink):
             
         return d + orphan_distance
 
-    def distance (self):
-        if type (self._distance) != type (0.0):
-            return self.calc_distance ()
-        
-        return self._distance
-
     def source_file (self):
         for ext in ('.ly', '.ly.txt'):
             if os.path.exists (self.base_names[1] + ext):
@@ -414,19 +583,16 @@ class SignatureFileLink (FileLink):
                            os.path.normpath (base2))
 
         def note_original (match):
-            self.original_name = match.group (1)
+            hash_to_original_name[os.path.basename (self.base_names[1])] = match.group (1)
             return ''
         
-        if not self.original_name:
-            self.original_name = os.path.split (base1)[1]
-
-            ## ugh: drop the .ly.txt
-            for ext in ('.ly', '.ly.txt'):
-                try:
-                    re.sub (r'\\sourcefilename "([^"]+)"',
-                            note_original, open (base1 + ext).read ())
-                except IOError:
-                    pass
+        ## ugh: drop the .ly.txt
+        for ext in ('.ly', '.ly.txt'):
+            try:
+                re.sub (r'\\sourcefilename "([^"]+)"',
+                        note_original, open (base1 + ext).read ())
+            except IOError:
+                pass
                 
         s1 = read_signature_file (f1)
         s2 = read_signature_file (f2)
@@ -436,7 +602,7 @@ class SignatureFileLink (FileLink):
         self.add_system_link (link, system_index[0])
 
     
-    def create_images (self, old_dir, new_dir, dest_dir):
+    def create_images (self, dest_dir):
 
         files_created = [[], []]
         for oldnew in (0, 1):
@@ -457,12 +623,13 @@ class SignatureFileLink (FileLink):
 
         return files_created
     
-    def link_files_for_html (self, old_dir, new_dir, dest_dir):
+    def link_files_for_html (self, dest_dir):
+        FileLink.link_files_for_html (self, dest_dir)
         to_compare = [[], []]
 
-        exts = ['.ly']
+        exts = []
         if options.create_images:
-            to_compare = self.create_images (old_dir, new_dir, dest_dir)
+            to_compare = self.create_images (dest_dir)
         else:
             exts += ['.png', '-page*png']
         
@@ -479,8 +646,8 @@ class SignatureFileLink (FileLink):
             for (old, new) in zip (to_compare[0], to_compare[1]):
                 compare_png_images (old, new, dest_dir)
 
-                
-    def html_record_string (self,  old_dir, new_dir):
+
+    def get_cell (self, oldnew):
         def img_cell (ly, img, name):
             if not name:
                 name = 'source'
@@ -488,13 +655,9 @@ class SignatureFileLink (FileLink):
                 name = '<tt>%s</tt>' % name
                 
             return '''
-<td align="center">
 <a href="%(img)s">
 <img src="%(img)s" style="border-style: none; max-width: 500px;">
 </a><br>
-<font size="-2">(<a href="%(ly)s">%(name)s</a>)
-</font>
-</td>
 ''' % locals ()
         def multi_img_cell (ly, imgs, name):
             if not name:
@@ -509,11 +672,7 @@ class SignatureFileLink (FileLink):
 
 
             return '''
-<td align="center">
 %(imgs_str)s
-<font size="-2">(<a href="%(ly)s">%(name)s</a>)
-</font>
-</td>
 ''' % locals ()
 
 
@@ -526,32 +685,17 @@ class SignatureFileLink (FileLink):
                 return multi_img_cell (base + '.ly', sorted (pages), name)
             else:
                 return img_cell (base + '.ly', base + '.png', name)
-            
 
-        html_2  = self.base_names[1] + '.html'
-        name = self.original_name
 
-        cell_1 = cell (self.base_names[0], name)
-        cell_2 = cell (self.base_names[1], name)
-        if options.compare_images:
-            cell_2 = cell_2.replace ('.png', '.compare.jpeg')
-        
-        html_entry = '''
-<tr>
-<td>
-%f<br>
-(<a href="%s">details</a>)
-</td>
-
-%s
-%s
-</tr>
-''' % (self.distance (), html_2, cell_1, cell_2)
 
-        return html_entry
+        str = cell (os.path.splitext (self.file_names[oldnew])[0], self.name ())  
+        if options.compare_images and oldnew == 1:
+            str = str.replace ('.png', '.compare.jpeg')
+            
+        return str
 
 
-    def html_system_details_string (self):
+    def get_distance_details (self):
         systems = self.system_links.items ()
         systems.sort ()
 
@@ -575,7 +719,7 @@ class SignatureFileLink (FileLink):
             e = '<tr>%s</tr>' % e
             html += e
             
-        original = self.original_name
+        original = self.name ()
         html = '''<html>
 <head>
 <title>comparison details for %(original)s</title>
@@ -597,11 +741,6 @@ class SignatureFileLink (FileLink):
 ''' % locals ()
         return html
 
-    def write_html_system_details (self, dir1, dir2, dest_dir):
-        dest_file =  os.path.join (dest_dir, self.base_names[1] + '.html')
-
-        details = open_write_file (dest_file)
-        details.write (self.html_system_details_string ())
 
 ################################################################
 # Files/directories
@@ -609,8 +748,6 @@ class SignatureFileLink (FileLink):
 import glob
 import re
 
-
-
 def compare_signature_files (f1, f2):
     s1 = read_signature_file (f1)
     s2 = read_signature_file (f2)
@@ -624,20 +761,23 @@ def paired_files (dir1, dir2, pattern):
     Return (PAIRED, MISSING-FROM-2, MISSING-FROM-1)
 
     """
-    
-    files1 = dict ((os.path.split (f)[1], 1) for f in glob.glob (dir1 + '/' + pattern))
-    files2 = dict ((os.path.split (f)[1], 1) for f in glob.glob (dir2 + '/' + pattern))
 
+    files = []
+    for d in (dir1,dir2):
+        found = [os.path.split (f)[1] for f in glob.glob (d + '/' + pattern)]
+        found = dict ((f, 1) for f in found)
+        files.append (found)
+        
     pairs = []
     missing = []
-    for f in files1.keys ():
+    for f in files[0].keys ():
         try:
-            files2.pop (f)
+            files[1].pop (f)
             pairs.append (f)
         except KeyError:
             missing.append (f)
 
-    return (pairs, files2.keys (), missing)
+    return (pairs, files[1].keys (), missing)
     
 class ComparisonData:
     def __init__ (self):
@@ -661,7 +801,7 @@ class ComparisonData:
                 self.compare_trees (d1, d2)
     
     def compare_directories (self, dir1, dir2):
-        for ext in ['signature', 'midi']:
+        for ext in ['signature', 'midi', 'log', 'profile', 'gittxt']:
             (paired, m1, m2) = paired_files (dir1, dir2, '*.' + ext)
 
             self.missing += [(dir1, m) for m in m1] 
@@ -670,7 +810,6 @@ class ComparisonData:
             for p in paired:
                 if (options.max_count
                     and len (self.file_links) > options.max_count):
-                    
                     continue
                 
                 f2 = dir2 +  '/' + p
@@ -680,13 +819,22 @@ class ComparisonData:
     def compare_files (self, f1, f2):
         if f1.endswith ('signature'):
             self.compare_signature_files (f1, f2)
-        elif f1.endswith ('midi'):
-            self.compare_midi_files (f1, f2)
+        else:
+            ext = os.path.splitext (f1)[1]
+            klasses = {
+                '.midi': MidiFileLink,
+                '.log' : TextFileCompareLink,
+                '.profile': ProfileFileLink,
+                '.gittxt': GitFileCompareLink, 
+                }
             
-    def compare_midi_files (self, f1, f2):
+            if klasses.has_key (ext):
+                self.compare_general_files (klasses[ext], f1, f2)
+
+    def compare_general_files (self, klass, f1, f2):
         name = os.path.split (f1)[1]
 
-        file_link = MidiFileLink (f1, f2)
+        file_link = klass (f1, f2)
         self.file_links[name] = file_link
         
     def compare_signature_files (self, f1, f2):
@@ -697,11 +845,35 @@ class ComparisonData:
         try:
             file_link = self.file_links[name]
         except KeyError:
-            file_link = SignatureFileLink ()
+            generic_f1 = re.sub ('-[0-9]+.signature', '.ly', f1)
+            generic_f2 = re.sub ('-[0-9]+.signature', '.ly', f2)
+            file_link = SignatureFileLink (generic_f1, generic_f2)
             self.file_links[name] = file_link
 
         file_link.add_file_compare (f1, f2)
 
+    def write_changed (self, dest_dir, threshold):
+        (changed, below, unchanged) = self.thresholded_results (threshold)
+
+        str = '\n'.join ([os.path.splitext (link.file_names[1])[0]
+                        for link in changed])
+        fn = dest_dir + '/changed.txt'
+        
+        open_write_file (fn).write (str)
+                
+    def thresholded_results (self, threshold):
+        ## todo: support more scores.
+        results = [(link.distance(), link)
+                   for link in self.file_links.values ()]
+        results.sort ()
+        results.reverse ()
+
+        unchanged = [r for (d,r) in results if d == 0.0]
+        below = [r for (d,r) in results if threshold >= d > 0.0]
+        changed = [r for (d,r) in results if d > threshold]
+
+        return (changed, below, unchanged)
+                
     def write_text_result_page (self, filename, threshold):
         out = None
         if filename == '':
@@ -710,21 +882,15 @@ class ComparisonData:
             print 'writing "%s"' % filename
             out = open_write_file (filename)
 
-        ## todo: support more scores.
-        results = [(link.distance(), link)
-                   for link in self.file_links.values ()]
-        results.sort ()
-        results.reverse ()
+        (changed, below, unchanged) = self.thresholded_results (threshold)
 
         
-        for (score, link) in results:
-            if score > threshold:
-                out.write (link.text_record_string ())
+        for link in changed:
+            out.write (link.text_record_string ())
 
         out.write ('\n\n')
-        out.write ('%d below threshold\n' % len ([1 for s,l  in results
-                                                    if threshold >=  s > 0.0]))
-        out.write ('%d unchanged\n' % len ([1 for (s,l) in results if s == 0.0]))
+        out.write ('%d below threshold\n' % len (below))
+        out.write ('%d unchanged\n' % len (unchanged))
         
     def create_text_result_page (self, dir1, dir2, dest_dir, threshold):
         self.write_text_result_page (dest_dir + '/index.txt', threshold)
@@ -732,22 +898,14 @@ class ComparisonData:
     def create_html_result_page (self, dir1, dir2, dest_dir, threshold):
         dir1 = dir1.replace ('//', '/')
         dir2 = dir2.replace ('//', '/')
-        
-        results = [(link.distance(), link)
-                   for link in self.file_links.values ()]
-        results.sort ()
-        results.reverse ()
+
+        (changed, below, unchanged) = self.thresholded_results (threshold)
+
 
         html = ''
         old_prefix = os.path.split (dir1)[1]
-        for (score, link) in results:
-            if score <= threshold:
-                continue
-
-            link.link_files_for_html (dir1, dir2, dest_dir) 
-            link.write_html_system_details (dir1, dir2, dest_dir)
-            
-            html += link.html_record_string (dir1, dir2)
+        for link in changed:
+            html += link.html_record_string (dest_dir)
 
 
         short_dir1 = shorten_string (dir1)
@@ -764,15 +922,12 @@ class ComparisonData:
 </html>''' % locals()
 
         html += ('<p>')
-        below_count  =len ([1 for s,l  in results
-                            if threshold >=  s > 0.0])
+        below_count = len (below)
 
         if below_count:
             html += ('<p>%d below threshold</p>' % below_count)
-
-        html += ('<p>%d unchanged</p>'
-                 % len ([1 for (s,l) in results if s == 0.0]))
-
+            
+        html += ('<p>%d unchanged</p>' % len (unchanged))
 
         dest_file = dest_dir + '/index.html'
         open_write_file (dest_file).write (html)
@@ -788,6 +943,7 @@ def compare_trees (dir1, dir2, dest_dir, threshold):
     if os.path.isdir (dest_dir):
         system ('rm -rf %s '% dest_dir)
 
+    data.write_changed (dest_dir, threshold)
     data.create_html_result_page (dir1, dir2, dest_dir, threshold)
     data.create_text_result_page (dir1, dir2, dest_dir, threshold)
     
@@ -802,6 +958,7 @@ def mkdir (x):
 def link_file (x, y):
     mkdir (os.path.split (y)[0])
     try:
+        print x, '->', y
         os.link (x, y)
     except OSError, z:
         print 'OSError', x, y, z
@@ -828,15 +985,17 @@ def test_paired_files ():
 def test_compare_trees ():
     system ('rm -rf dir1 dir2')
     system ('mkdir dir1 dir2')
-    system ('cp 20{-*.signature,.ly,.png,.eps} dir1')
-    system ('cp 20{-*.signature,.ly,.png,.eps} dir2')
-    system ('cp 20expr{-*.signature,.ly,.png,.eps} dir1')
-    system ('cp 19{-*.signature,.ly,.png,.eps} dir2/')
-    system ('cp 19{-*.signature,.ly,.png,.eps} dir1/')
-    system ('cp 19-1.signature 19-sub-1.signature')
-    system ('cp 19.ly 19-sub.ly')
-    system ('cp 19.png 19-sub.png')
-    system ('cp 19.eps 19-sub.eps')
+    system ('cp 20{-*.signature,.ly,.png,.eps,.log,.profile} dir1')
+    system ('cp 20{-*.signature,.ly,.png,.eps,.log,.profile} dir2')
+    system ('cp 20expr{-*.signature,.ly,.png,.eps,.log,.profile} dir1')
+    system ('cp 19{-*.signature,.ly,.png,.eps,.log,.profile} dir2/')
+    system ('cp 19{-*.signature,.ly,.png,.eps,.log,.profile} dir1/')
+    system ('cp 19-1.signature 19.sub-1.signature')
+    system ('cp 19.ly 19.sub.ly')
+    system ('cp 19.profile 19.sub.profile')
+    system ('cp 19.log 19.sub.log')
+    system ('cp 19.png 19.sub.png')
+    system ('cp 19.eps 19.sub.eps')
 
     system ('cp 20multipage* dir1')
     system ('cp 20multipage* dir2')
@@ -844,37 +1003,41 @@ def test_compare_trees ():
 
     
     system ('mkdir -p dir1/subdir/ dir2/subdir/')
-    system ('cp 19-sub{-*.signature,.ly,.png,.eps} dir1/subdir/')
-    system ('cp 19-sub{-*.signature,.ly,.png,.eps} dir2/subdir/')
-    system ('cp 20grob{-*.signature,.ly,.png,.eps} dir2/')
-    system ('cp 20grob{-*.signature,.ly,.png,.eps} dir1/')
+    system ('cp 19.sub{-*.signature,.ly,.png,.eps,.log,.profile} dir1/subdir/')
+    system ('cp 19.sub{-*.signature,.ly,.png,.eps,.log,.profile} dir2/subdir/')
+    system ('cp 20grob{-*.signature,.ly,.png,.eps,.log,.profile} dir2/')
+    system ('cp 20grob{-*.signature,.ly,.png,.eps,.log,.profile} dir1/')
+    system ('echo HEAD is 1 > dir1/tree.gittxt')
+    system ('echo HEAD is 2 > dir2/tree.gittxt')
 
     ## introduce differences
     system ('cp 19-1.signature dir2/20-1.signature')
+    system ('cp 19.profile dir2/20.profile')
     system ('cp 19.png dir2/20.png')
     system ('cp 19multipage-page1.png dir2/20multipage-page1.png')
-    system ('cp 20-1.signature dir2/subdir/19-sub-1.signature')
-    system ('cp 20.png dir2/subdir/19-sub.png')
+    system ('cp 20-1.signature dir2/subdir/19.sub-1.signature')
+    system ('cp 20.png dir2/subdir/19.sub.png')
+    system ("sed 's/: /: 1/g'  20.profile > dir2/subdir/19.sub.profile")
 
     ## radical diffs.
     system ('cp 19-1.signature dir2/20grob-1.signature')
     system ('cp 19-1.signature dir2/20grob-2.signature')
     system ('cp 19multipage.midi dir1/midi-differ.midi')
     system ('cp 20multipage.midi dir2/midi-differ.midi')
+    system ('cp 19multipage.log dir1/log-differ.log')
+    system ('cp 19multipage.log dir2/log-differ.log &&  echo different >> dir2/log-differ.log &&  echo different >> dir2/log-differ.log')
 
-    compare_trees ('dir1', 'dir2', 'compare-dir1dir2', 0.5)
+    compare_trees ('dir1', 'dir2', 'compare-dir1dir2', options.threshold)
 
 
 def test_basic_compare ():
     ly_template = r"""
 
 \version "2.10.0"
-#(set! toplevel-score-handler print-score-with-defaults)
- #(set! toplevel-music-handler
-  (lambda (p m)
-  (if (not (eq? (ly:music-property m 'void) #t))
-     (print-score-with-defaults
-     p (scorify-music m p)))))
+#(define default-toplevel-book-handler
+  print-book-with-defaults-as-systems )
+
+#(ly:set-option (quote no-point-and-click))
 
 \sourcefilename "my-source.ly"
  
@@ -916,8 +1079,8 @@ def test_basic_compare ():
         open (d['name'] + '.ly','w').write (ly_template % d)
         
     names = [d['name'] for d in dicts]
-    
-    system ('lilypond -ddump-signatures --png -b eps ' + ' '.join (names))
+
+    system ('lilypond -ddump-profile -dseparate-log-files -ddump-signatures --png -b eps ' + ' '.join (names))
     
 
     multipage_str = r'''
@@ -929,9 +1092,9 @@ def test_basic_compare ():
     }
     '''
 
-    open ('20multipage', 'w').write (multipage_str.replace ('c1', 'd1'))
-    open ('19multipage', 'w').write ('#(set-global-staff-size 19.5)\n' + multipage_str)
-    system ('lilypond -ddump-signatures --png 19multipage 20multipage ')
+    open ('20multipage.ly', 'w').write (multipage_str.replace ('c1', 'd1'))
+    open ('19multipage.ly', 'w').write ('#(set-global-staff-size 19.5)\n' + multipage_str)
+    system ('lilypond -dseparate-log-files -ddump-signatures --png 19multipage 20multipage ')
  
     test_compare_signatures (names)
     
index edff9c2f7ad376fb5a74658698a0c6ba53ae338f..481dbca4b48e80d60284f531818d266f2bdb0581 100644 (file)
@@ -5,7 +5,7 @@
 
 }
 
-\version  "2.11.10"
+\version  "2.10.10"
 
 \score {
   \relative {
diff --git a/input/regression/part-combine-tuplet-end.ly b/input/regression/part-combine-tuplet-end.ly
new file mode 100644 (file)
index 0000000..b6bf597
--- /dev/null
@@ -0,0 +1,19 @@
+\header {
+  
+  texidoc = "End tuplets events are sent to the starting context, so
+even after a switch, a tuplet ends correctly."
+  
+}
+
+\version "2.10.8"
+
+\new Staff <<
+  \partcombine
+  \relative c'' {
+    r2
+    \times 2/3 { g8[ g g] }
+    \times 2/3 { g[ g g] } g1
+  }
+  \relative c'' { R1 g1 }
+>>
+
diff --git a/input/regression/part-combine-tuplet-single.ly b/input/regression/part-combine-tuplet-single.ly
new file mode 100644 (file)
index 0000000..879e163
--- /dev/null
@@ -0,0 +1,22 @@
+
+\header {
+  texidoc = "Tuplets in combined parts only print one bracket."
+  }
+
+\paper { ragged-right = ##T }
+
+\version "2.10.10"
+
+\score {
+  <<
+    \new Staff {
+      \partcombine
+      \relative c'' {
+        \times 2/3 { d4 d d ~ } d2
+      }
+      \relative c'' {
+        \times 2/3 { b4 a g ~ } g2
+      }
+    }
+  >>
+}
diff --git a/input/regression/part-combine-tuplet.ly b/input/regression/part-combine-tuplet.ly
deleted file mode 100644 (file)
index b6bf597..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-\header {
-  
-  texidoc = "End tuplets events are sent to the starting context, so
-even after a switch, a tuplet ends correctly."
-  
-}
-
-\version "2.10.8"
-
-\new Staff <<
-  \partcombine
-  \relative c'' {
-    r2
-    \times 2/3 { g8[ g g] }
-    \times 2/3 { g[ g g] } g1
-  }
-  \relative c'' { R1 g1 }
->>
-
diff --git a/input/regression/quote-tuplet-end.ly b/input/regression/quote-tuplet-end.ly
new file mode 100644 (file)
index 0000000..cbf4752
--- /dev/null
@@ -0,0 +1,22 @@
+\header
+{
+  texidoc  ="Tuplet bracket ends properly when quoting."
+}
+
+\version "2.11.10"
+
+\paper { ragged-right = ##t }
+
+\addQuote x {
+   \times 2/3 { a'8 a' a' } a'4 a'2 |
+}
+
+\new Staff <<
+   \set Staff.quotedEventTypes = #'(note-event tuplet-span-event)
+   \new Voice = "cue" { s1 }
+   \new Voice {
+       \cueDuring #"x" #1 { r4 }
+       c'4
+       \cueDuring #"x" #1 { r2 }
+   }
+>>
index 973a1d8c5eb696d13f802ca1c590b268431fd578..ca33ea786767fb7664712c723def8b4f5f1995b9 100644 (file)
@@ -12,6 +12,7 @@
 #include "international.hh"
 #include "music.hh"
 #include "music-wrapper-iterator.hh"
+#include "stream-event.hh"
 
 /*
   Iterates \times, by sending TupletSpanEvents at the start/end of each
@@ -30,6 +31,9 @@ protected:
   virtual void construct_children ();
   virtual void derived_mark () const;
   virtual Moment pending_moment () const;
+
+  Music *create_event (Direction d);
+  
 private:
 
   /* tupletSpannerDuration */
@@ -39,34 +43,47 @@ private:
   Moment next_split_mom_;
   
   /* Recycle start/stop events if tupletSpannerDuration is set. */
-  Music *start_;
-  Music *stop_;
-
-  bool done_first_;
+  SCM synthesized_events_;
   
   Context_handle tuplet_handler_;
 };
 
+Music*
+Time_scaled_music_iterator::create_event (Direction d)
+{
+  SCM ev_scm = scm_call_2 (ly_lily_module_constant ("make-span-event"),
+                          ly_symbol2scm ("TupletSpanEvent"),
+                          scm_from_int (d));
+  
+  Music *mus = get_music ();
+
+  Music *ev = unsmob_music (ev_scm);
+  ev->set_spot (*mus->origin ());
+  if (d == START)
+    {
+      ev->set_property ("numerator", mus->get_property ("numerator"));
+      ev->set_property ("denominator", mus->get_property ("denominator"));
+      ev->set_property ("tweaks", mus->get_property ("tweaks"));
+      ev->set_property ("length", spanner_duration_.smobbed_copy ()); 
+    }  
+
+  synthesized_events_ = scm_cons (ev_scm, synthesized_events_);
+  return ev;
+}
+
+
 Time_scaled_music_iterator::Time_scaled_music_iterator ()
 {
   spanner_duration_ = next_split_mom_ = 0;
-  done_first_ = 0;
+  synthesized_events_ = SCM_EOL;
 }
 
 
 Moment
 Time_scaled_music_iterator::pending_moment () const
 {
-  if (!done_first_)
-    return Moment (0);
-  
   Moment next_mom = Music_wrapper_iterator::pending_moment ();
-
-  if (spanner_duration_.to_bool () &&
-      next_mom.main_part_ > next_split_mom_)
-    {
-      next_mom = next_split_mom_;
-    }
+  next_mom = min (next_mom, next_split_mom_);
 
   return next_mom;
 }
@@ -75,75 +92,44 @@ Time_scaled_music_iterator::pending_moment () const
 void
 Time_scaled_music_iterator::process (Moment m)
 {
-  if (!done_first_)
-    {
-      done_first_ = true;
-      descend_to_bottom_context ();
-      report_event (start_);
-      tuplet_handler_.set_context (get_outlet());
-    }
-
   if (spanner_duration_.to_bool () &&
       m.main_part_ == next_split_mom_)
     {
       descend_to_bottom_context ();
-      stop_->send_to_context (tuplet_handler_.get_outlet ());
-      
-      tuplet_handler_.set_context (get_outlet ());
-      report_event (start_);
+      if (tuplet_handler_.get_outlet())
+       create_event (STOP)->send_to_context (tuplet_handler_.get_outlet ());
+
+      if (m.main_part_ < music_get_length ().main_part_)
+       {
+         tuplet_handler_.set_context (get_outlet ());
+         report_event (create_event (START));
       
-      next_split_mom_ += spanner_duration_;
-      /* avoid sending events twice at the end */
-      if (next_split_mom_ == get_music ()->get_length ().main_part_)
-       next_split_mom_.set_infinite (1);
+         next_split_mom_ += spanner_duration_;
+       }
+      else
+       {
+         tuplet_handler_.set_context (0);
+       }
     }
-
   Music_wrapper_iterator::process(m);
   if (child_iter_ && child_iter_->ok ())
     descend_to_child (child_iter_->get_outlet ());
   
-  if (m.main_part_ == music_get_length ().main_part_)
-    {
-      stop_->send_to_context (tuplet_handler_.get_outlet ());
-      tuplet_handler_.set_context (0);
-    }
 }
 
 void
 Time_scaled_music_iterator::construct_children ()
 {
-  /*
-    Inheritance trickery:
-    Time_scaled_music_iterator::construct_children initialises start_
-    and stop_, and calls Sequential_music::construct_children, which
-    in turn calls Time_scaled_music_iterator::get_music which reads
-    start_ and stop_.
-   */
-
-  Music *mus = get_music ();
-  Input *origin = mus->origin ();
-
-  SCM tuplet_symbol = ly_symbol2scm ("TupletSpanEvent");
-  SCM start_scm = scm_call_2 (ly_lily_module_constant ("make-span-event"), tuplet_symbol, scm_from_int (START));
-  start_ = unsmob_music (start_scm);
-  start_->set_spot (*origin);
-  start_->set_property ("numerator", mus->get_property ("numerator"));
-  start_->set_property ("denominator", mus->get_property ("denominator"));
-  start_->set_property ("tweaks", mus->get_property ("tweaks"));
-  
-
-  SCM stop_scm = scm_call_2 (ly_lily_module_constant ("make-span-event"), tuplet_symbol, scm_from_int (STOP));
-  stop_ = unsmob_music (stop_scm);
-  stop_->set_spot (*origin);
-
   Moment *mp = unsmob_moment (get_outlet ()->get_property ("tupletSpannerDuration"));
-
   if (mp)
     {
       spanner_duration_ = mp->main_part_;
-      next_split_mom_ = spanner_duration_;
     }
-
+  else
+    {
+      spanner_duration_ = music_get_length ();
+    }
+  
   Music_wrapper_iterator::construct_children ();
 
   if (child_iter_ && child_iter_->ok ())
@@ -153,11 +139,7 @@ Time_scaled_music_iterator::construct_children ()
 void
 Time_scaled_music_iterator::derived_mark () const
 {
-  if (start_)
-    scm_gc_mark (start_->self_scm ());
-  if (stop_)
-    scm_gc_mark (stop_->self_scm ());
-
+  scm_gc_mark (synthesized_events_);
   Music_wrapper_iterator::derived_mark ();
 }
 
index f8748b2b197a53e7f7b46b61eb9d1d36e38b349a..7592e98a08d0dcc9fd553b30691755672401e95a 100644 (file)
@@ -14,6 +14,8 @@
 #include "stream-event.hh"
 #include "tuplet-bracket.hh"
 #include "warn.hh"
+#include "item.hh"
+#include "moment.hh"
 
 #include "translator.icc"
 
@@ -25,6 +27,9 @@ struct Tuplet_description
 
   bool full_length_;
   bool full_length_note_;
+  Moment stop_moment_;
+  Moment start_moment_;
+  Moment length_;
   
   Tuplet_description ()
   {
@@ -63,20 +68,51 @@ Tuplet_engraver::listen_tuplet_span (Stream_event *ev)
     {
       Tuplet_description d;
       d.event_ = ev;
+
+      d.length_ = robust_scm2moment (d.event_->get_property ("length"),
+                                    Moment (0));
+      d.start_moment_ = now_mom ();
+      d.stop_moment_ = now_mom () + d.length_;
+
+      for (vsize i=0; i < new_tuplets_.size (); i++)
+       {
+         /*
+           discard duplicates.
+          */
+         if (new_tuplets_[i].stop_moment_ == d.stop_moment_)
+           return;
+       }
+      
       new_tuplets_.push_back (d);
     }
-  else if (dir == STOP && tuplets_.size ())
+  else if (dir == STOP)
+    {
+    if (tuplets_.size ())
     {
       stopped_tuplets_.push_back (tuplets_.back ());
       tuplets_.pop_back ();
     }
+    else
+      ev->origin ()->warning (_f ("No tuplet to end"));
+    }
   else 
-    programming_error (_ ("invalid direction of tuplet-span-event"));
+    ev->origin ()->programming_error ("direction tuplet-span-event invalid.");
 }
 
 void
 Tuplet_engraver::process_music ()
 {
+  /*
+    This may happen if the end of a tuplet is part of a quoted voice.
+   */
+  Moment now = now_mom();
+  for (vsize i = tuplets_.size (); i --; )
+    {
+      stopped_tuplets_.push_back (tuplets_[i]);
+      if (tuplets_[i].stop_moment_ == now)
+       tuplets_.erase (tuplets_.begin () + i);
+    }
+  
   for (vsize i = 0; i < stopped_tuplets_.size (); i++)
     {
       Spanner *bracket = stopped_tuplets_[i].bracket_;
@@ -100,6 +136,7 @@ Tuplet_engraver::process_music ()
              number->set_bound (RIGHT,
                                                      stopped_tuplets_[i].bracket_->get_bound (LEFT));
            }
+         
          // todo: scrap last_tuplets_, use stopped_tuplets_ only.
          // clear stopped_tuplets_ at start_translation_timestep
          last_tuplets_.push_back (bracket);
@@ -129,6 +166,8 @@ Tuplet_engraver::process_music ()
                                          tuplets_[i].event_->self_scm ());
       tuplets_[i].number_->set_object ("bracket", tuplets_[i].bracket_->self_scm ());
       tuplets_[i].bracket_->set_object ("tuplet-number", tuplets_[i].number_->self_scm ());
+      tuplets_[i].stop_moment_.grace_part_ = 0;
+      
       
       if (i < tuplets_.size () - 1 && tuplets_[i + 1].bracket_)
        Tuplet_bracket::add_tuplet_bracket (tuplets_[i].bracket_, tuplets_[i + 1].bracket_);
index 82aa83f3b46d573e1c461a5c6e8affde1332bf14..01e63952ecdb68e51cdd6a65e7089702698f96a7 100644 (file)
@@ -10,6 +10,8 @@
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; general
+(define-public (grob::has-interface grob iface)
+  (memq iface (ly:grob-interfaces grob)))
 
 (define-public (make-stencil-boxer thickness padding callback)