]> git.donarmstrong.com Git - lilypond.git/commitdiff
Add support for multi-note tremolo
authorReinhold Kainhofer <reinhold@kainhofer.com>
Sat, 10 Jul 2010 09:10:02 +0000 (11:10 +0200)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Tue, 13 Jul 2010 11:34:56 +0000 (13:34 +0200)
-) Remove all checks for exactly one or two notes as arguments to a
   tremolo repeat
-) Create proper beaming for multi-note tremolo (so far, the
   chord-tremolo-engraver always assumed exactly two beamed notes)
-) Extend the make-repeat function to scale the notes properly also
   in the case of multi-note tremolos

patch from issue 1786041 on codereview.appspot.com

input/regression/repeat-tremolo-three-notes.ly [new file with mode: 0644]
lily/chord-tremolo-engraver.cc
lily/chord-tremolo-iterator.cc
scm/music-functions.scm

diff --git a/input/regression/repeat-tremolo-three-notes.ly b/input/regression/repeat-tremolo-three-notes.ly
new file mode 100644 (file)
index 0000000..210ce39
--- /dev/null
@@ -0,0 +1,19 @@
+\version "2.13.28"
+
+\header {
+  texidoc = "A tremolo can have more than two notes. Also check that
+linebreaks between tremolos still work and that empty tremolos don't crash."
+}
+
+\paper { ragged-right = ##t }
+\score {
+  \new Staff \relative c' {
+    \time 3/4
+    \repeat tremolo 16 { a64 c e } |
+    \repeat tremolo 8 { a,64 c e }
+    \repeat tremolo 4 { a,64 c e }
+    \repeat tremolo 2 { a,32 c e } |\break
+    \repeat tremolo 16 { a64 c e }
+    \repeat tremolo 8 {} |
+  }
+}
index 141f7313e45d266c8489137d16c97cff1a411d98..f0ac33d25ab0f134a62e1ff0042bc04e35981170 100644 (file)
@@ -54,10 +54,10 @@ class Chord_tremolo_engraver : public Engraver
 protected:
   Stream_event *repeat_;
 
-  // current direction of beam (first RIGHT, then LEFT)
-  Direction beam_dir_;
-
   Spanner *beam_;
+  // Store the pointer to the previous stem, so we can create a beam if
+  // necessary and end the spanner
+  Grob *previous_stem_;
 
 protected:
   virtual void finalize ();
@@ -70,7 +70,7 @@ Chord_tremolo_engraver::Chord_tremolo_engraver ()
 {
   beam_ = 0;
   repeat_ = 0;
-  beam_dir_ = CENTER;
+  previous_stem_ = 0;
 }
 
 IMPLEMENT_TRANSLATOR_LISTENER (Chord_tremolo_engraver, tremolo_span);
@@ -80,18 +80,15 @@ Chord_tremolo_engraver::listen_tremolo_span (Stream_event *ev)
   Direction span_dir = to_dir (ev->get_property ("span-direction"));
   if (span_dir == START)
     {
-      if (ASSIGN_EVENT_ONCE (repeat_, ev))
-       {
-         beam_dir_ = RIGHT;
-       }
+      ASSIGN_EVENT_ONCE (repeat_, ev);
     }
   else if (span_dir == STOP)
     {
       if (!repeat_)
-       ev->origin ()->warning (_ ("No tremolo to end"));
+        ev->origin ()->warning (_ ("No tremolo to end"));
       repeat_ = 0;
       beam_ = 0;
-      beam_dir_ = CENTER;
+      previous_stem_ = 0;
     }
 }
 
@@ -126,17 +123,26 @@ Chord_tremolo_engraver::acknowledge_stem (Grob_info info)
       int gap_count = min (flags, intlog2 (repeat_count) + 1);
 
       Grob *s = info.grob ();
-      Stem::set_beaming (s, flags, beam_dir_);
+      if (previous_stem_)
+        {
+          // FIXME: We know that the beam has ended only in listen_tremolo_span
+          //        but then it is too late for Spanner_break_forbid_engraver
+          //        to allow a line break... So, as a nasty hack, announce the
+          //        spanner's end after each note except the first. The only
+          //        "drawback" is that for multi-note tremolos a break would
+          //        theoretically be allowed after the second note (but since
+          //        that note is typically not at a barline, I don't think
+          //        anyone will ever notice!)
+          announce_end_grob (beam_, previous_stem_->self_scm ());
+          // Create the whole beam between previous and current note
+          Stem::set_beaming (previous_stem_, flags, RIGHT);
+          Stem::set_beaming (s, flags, LEFT);
+        }
 
       if (Stem::duration_log (s) != 1)
-       beam_->set_property ("gap-count", scm_from_int (gap_count));
+        beam_->set_property ("gap-count", scm_from_int (gap_count));
+
 
-      if (beam_dir_ == RIGHT)
-       {
-         beam_dir_ = LEFT;
-         announce_end_grob (beam_, s->self_scm ());
-       }
-      
       if (info.ultimate_event_cause ()->in_event_class ("rhythmic-event"))
        Beam::add_stem (beam_, s);
       else
@@ -147,6 +153,9 @@ Chord_tremolo_engraver::acknowledge_stem (Grob_info info)
          else
            ::warning (s);
        }
+      // Store current grob, so we can possibly end the spanner here (and
+      // reset the beam direction to RIGHT)
+      previous_stem_ = s;
     }
 }
 
index e6c4dd604abc8b524a758ebcfa0b17c714dcb8b1..4a64221fa9f6bfeb836935336bb1f9aa2b0aeb12 100644 (file)
@@ -40,11 +40,6 @@ Chord_tremolo_iterator::get_music_list () const
 
   int elt_count = body_is_sequential ? scm_ilength (body->get_property ("elements")) : 1;
 
-  if (body_is_sequential &&
-      (elt_count != 2
-       && elt_count != 1))
-    mus->origin ()->warning (_f ("expect 2 elements for chord tremolo, found %d", elt_count));
-
   if (elt_count <= 0)
     elt_count = 1;
 
@@ -57,7 +52,7 @@ Chord_tremolo_iterator::get_music_list () const
       return scm_list_2 (ev->unprotect (), body->self_scm ());
     }
   else
-    { 
+    {
       SCM tremolo_symbol = ly_symbol2scm ("TremoloSpanEvent");
       SCM start_event_scm = scm_call_2 (ly_lily_module_constant ("make-span-event"), tremolo_symbol, scm_from_int (START));
       unsmob_music (start_event_scm)->set_spot (*origin);
index 57f17baaa2a6588777d535457f07c06ab2ae4d47..9f173ff7641d6544e73852dddc47e232a9fa32e9 100644 (file)
@@ -265,9 +265,14 @@ through MUSIC."
     (set! (ly:music-property r 'element) main)
     (set! (ly:music-property r 'repeat-count) (max times 1))
     (set! (ly:music-property r 'elements) talts)
-    (if (equal? name "tremolo")
-       (let* ((dots (1- (logcount times)))
-              (mult (/ (* times (ash 1 dots)) (1- (ash 2 dots))))
+    (if (and (equal? name "tremolo") (> (length (ly:music-property main 'elements)) 0))
+       ;; This works for single-note and multi-note tremolos!
+       (let* ((children (length (ly:music-property main 'elements)))
+              ;; # of dots is equal to the 1 in bitwise representation (minus 1)!
+              (dots (1- (logcount (* times children))))
+              ;; The remaining missing multiplicator to scale the notes by 
+              ;; times * children
+              (mult (/ (* times children (ash 1 dots)) (1- (ash 2 dots))))
               (shift (- (ly:intlog2 (floor mult))))
               (note-duration (first-note-duration r))
               (duration-log (if (ly:duration? note-duration)
@@ -277,20 +282,11 @@ through MUSIC."
          (set! (ly:music-property r 'tremolo-type) tremolo-type)
          (if (not (integer?  mult))
               (ly:warning (_ "invalid tremolo repeat count: ~a") times))
-         (if (memq 'sequential-music (ly:music-property main 'types))
-             ;; \repeat "tremolo" { c4 d4 }
-             (let ((children (length (ly:music-property main 'elements))))
-
-               ;; fixme: should be more generic.
-               (if (and (not (= children 2))
-                        (not (= children 1)))
-                   (ly:warning (_ "expecting 2 elements for chord tremolo, found ~a") children))
-               (ly:music-compress r (ly:make-moment 1 children))
-               (shift-duration-log r
-                                   (if (= children 2)  (1- shift) shift)
-                                   dots))
-             ;; \repeat "tremolo" c4
-             (shift-duration-log r shift dots)))
+         ;; \repeat tremolo n c4
+         ;; Adjust the time of the notes
+         (ly:music-compress r (ly:make-moment 1 children))
+         ;; Adjust the displayed note durations
+         (shift-duration-log r shift dots))
        r)))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;