From: Keith OHara <k-ohara5a5a@oco.net>
Date: Sat, 30 Nov 2013 02:01:21 +0000 (-0800)
Subject: Completion_*_engraver: add means to preserve scale factor; issue 3650
X-Git-Tag: release/2.19.0-1~99
X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=9f909143a605a677787915e5dcce5dbc48f2211c;p=lilypond.git

Completion_*_engraver: add means to preserve scale factor; issue 3650
---

diff --git a/Documentation/notation/rhythms.itely b/Documentation/notation/rhythms.itely
index 2847d627c1..f935956922 100644
--- a/Documentation/notation/rhythms.itely
+++ b/Documentation/notation/rhythms.itely
@@ -1810,6 +1810,37 @@ inserts ties for notes.  One of its uses is to debug complex scores: if
 the measures are not entirely filled, then the ties show exactly how
 much each measure is off.
 
+The property @code{completionUnit} sets a preferred duration for
+the split notes.
+
+@lilypond[quote,verbatim,relative=2]
+\new Voice \with {
+  \remove "Note_heads_engraver"
+  \consists "Completion_heads_engraver"
+} {
+  \time 9/8 g\breve. d4. \bar "||"
+  \set completionUnit = #(ly:make-moment 3 8)
+  g\breve. d4.
+}
+@end lilypond
+
+These engravers split notes with scaled duration, such as those in tuplets,
+into notes with the same scale-factor as in the input note.
+
+@lilypond[quote,verbatim,relative=2]
+\new Voice \with {
+  \remove "Note_heads_engraver"
+  \consists "Completion_heads_engraver"
+} {
+  \time 2/4 r4
+  \tuplet 3/2 {g4 a b}
+  \scaleDurations 2/3 {g a b}
+  g4*2/3 a b
+  \tuplet 3/2 {g4 a b}
+  r4
+}
+@end lilypond
+
 @seealso
 Music Glossary:
 @rglos{tie}
@@ -1829,12 +1860,12 @@ Internals Reference:
 @rinternals{Forbid_line_break_engraver}.
 
 @knownissues
-Not all durations (especially those containing tuplets) can be
-represented exactly with normal notes and dots, but the
-@code{Completion_heads_engraver} will not insert tuplets.
-
-The @code{Completion_heads_engraver} only affects notes; it does not
-split rests.
+For consistency with previous behavior, notes and rests with
+duration longer than a measure, such as @code{c1*2}, are split into
+notes without any scale factor, @code{@{ c1 c1 @}}.  The property
+@code{completionFactor} controls this behavior, and setting it to
+@code{#f} cause split notes and rest to have the scale factor
+of the input durations.
 
 
 @node Showing melody rhythms
diff --git a/input/regression/completion-heads-factor.ly b/input/regression/completion-heads-factor.ly
index 79510e1484..cc29f58d4e 100644
--- a/input/regression/completion-heads-factor.ly
+++ b/input/regression/completion-heads-factor.ly
@@ -1,12 +1,12 @@
-\version "2.16.0"
+\version "2.19.0"
 
 \header{
 texidoc="
 
 If the @code{Note_heads_engraver} is replaced by the @code{Completion_heads_engraver},
-notes with a duration factor still keep their requested appearance.
-
-"
+long notes, longer than @code{measureLength}, are split into un-scaled notes,
+even if the original note used a scale-factor.
+@code{completionFactor} controls this behavior."
 }
 
 \layout { ragged-right= ##t }
@@ -20,5 +20,11 @@ notes with a duration factor still keep their requested appearance.
   c\breve |
   c1*2 |
   c2*4 |
-  c8*20
+  c8*20 r2 \break
+  \tuplet 3/2 { d1 d d }
+  % \breve*2/3 is longer than a measure, but we want a tuplet, not repeats.
+  \set completionFactor = ##f
+  \tuplet 3/2 { e\breve e e }
+  \set completionFactor = #2/3
+  \tuplet 3/2 { e\breve e e }
 }
diff --git a/input/regression/completion-rest.ly b/input/regression/completion-rest.ly
index e39e17aff8..365b7372e2 100644
--- a/input/regression/completion-rest.ly
+++ b/input/regression/completion-rest.ly
@@ -1,12 +1,12 @@
-\version "2.16.0"
+\version "2.19.0"
 
 \header{
 texidoc="
 
 If the @code{Rest_engraver} is replaced by the @code{Completion_rest_engraver},
-rests with a duration factor still keep their requested appearance.
-
-"
+long rests, longer than @code{measureLength}, are split into
+un-scaled rests, even if the original duration used a scale-factor.
+@code{completionFactor} controls this behavior."
 }
 
 \layout { ragged-right= ##t }
@@ -20,5 +20,9 @@ rests with a duration factor still keep their requested appearance.
   r\breve |
   r1*2 |
   r2*4 |
-  r8*20
+  r8*20 r2 \break
+  \bar "||" \time 2/4
+  r\breve.*2/3
+  \set completionFactor = #1/2
+  r\breve.*2/3^"explicity request r1*1/2 rests"
 }
diff --git a/lily/completion-note-heads-engraver.cc b/lily/completion-note-heads-engraver.cc
index 52a7d6c0bd..bddca70abb 100644
--- a/lily/completion-note-heads-engraver.cc
+++ b/lily/completion-note-heads-engraver.cc
@@ -188,25 +188,24 @@ Completion_heads_engraver::process_music ()
         note that note_dur may be strictly less than left_to_do_
         (say, if left_to_do_ == 5/8)
       */
-      if (factor_.denominator () == 1 && factor_ > Rational (1, 1))
-        note_dur = Duration (left_to_do_, false);
-      else
-        note_dur = Duration (left_to_do_ / factor_, false).compressed (factor_);
+      note_dur = Duration (left_to_do_ / factor_, false).compressed (factor_);
     }
   else
     {
       orig = unsmob_duration (note_events_[0]->get_property ("duration"));
       note_dur = *orig;
-      factor_ = note_dur.factor ();
+      SCM factor = get_property ("completionFactor");
+      if (ly_is_procedure (factor))
+        factor = scm_call_2 (factor,
+                             context ()->self_scm (),
+                             note_dur.smobbed_copy ());
+      factor_ = robust_scm2rational (factor, note_dur.factor ());
       left_to_do_ = orig->get_length ();
     }
   Moment nb = next_moment (note_dur.get_length ());
   if (nb.main_part_ && nb < note_dur.get_length ())
     {
-      if (factor_.denominator () == 1 && factor_.numerator () > 1)
-        note_dur = Duration (nb.main_part_, false);
-      else
-        note_dur = Duration (nb.main_part_ / factor_, false).compressed (factor_);
+      note_dur = Duration (nb.main_part_ / factor_, false).compressed (factor_);
     }
 
   do_nothing_until_ = now.main_part_ + note_dur.get_length ();
@@ -314,6 +313,7 @@ ADD_TRANSLATOR (Completion_heads_engraver,
                 "TieColumn ",
 
                 /* read */
+                "completionFactor "
                 "completionUnit "
                 "measureLength "
                 "measurePosition "
diff --git a/lily/completion-rest-engraver.cc b/lily/completion-rest-engraver.cc
index 61255226ea..aeb6673f37 100644
--- a/lily/completion-rest-engraver.cc
+++ b/lily/completion-rest-engraver.cc
@@ -184,25 +184,24 @@ Completion_rest_engraver::process_music ()
         note that rest_dur may be strictly less than left_to_do_
         (say, if left_to_do_ == 5/8)
       */
-      if (factor_.denominator () == 1 && factor_ > Rational (1, 1))
-        rest_dur = Duration (left_to_do_, false);
-      else
-        rest_dur = Duration (left_to_do_ / factor_, false).compressed (factor_);
+      rest_dur = Duration (left_to_do_ / factor_, false).compressed (factor_);
     }
   else
     {
       orig = unsmob_duration (rest_events_[0]->get_property ("duration"));
       rest_dur = *orig;
-      factor_ = rest_dur.factor ();
+      SCM factor = get_property ("completionFactor");
+      if (ly_is_procedure (factor))
+        factor = scm_call_2 (factor,
+                             context ()->self_scm (),
+                             rest_dur.smobbed_copy ());
+      factor_ = robust_scm2rational (factor, rest_dur.factor());
       left_to_do_ = orig->get_length ();
     }
   Moment nb = next_moment (rest_dur.get_length ());
   if (nb.main_part_ && nb < rest_dur.get_length ())
     {
-      if (factor_.denominator () == 1 && factor_.numerator () > 1)
-        rest_dur = Duration (nb.main_part_, false);
-      else
-        rest_dur = Duration (nb.main_part_ / factor_, false).compressed (factor_);
+      rest_dur = Duration (nb.main_part_ / factor_, false).compressed (factor_);
     }
 
   do_nothing_until_ = now.main_part_ + rest_dur.get_length ();
@@ -268,6 +267,7 @@ ADD_TRANSLATOR (Completion_rest_engraver,
                 "Rest ",
 
                 /* read */
+                "completionFactor "
                 "completionUnit "
                 "middleCPosition "
                 "measurePosition "
diff --git a/ly/engraver-init.ly b/ly/engraver-init.ly
index ff777875c6..dfaa47e12f 100644
--- a/ly/engraver-init.ly
+++ b/ly/engraver-init.ly
@@ -634,6 +634,8 @@ automatically when an output definition (a @code{\\score} or
   autoBeaming = ##t
   autoBeamCheck = #default-auto-beam-check
 
+  completionFactor = #unity-if-multimeasure
+
   scriptDefinitions = #default-script-alist
 
   pedalSustainStrings = #'("Ped." "*Ped." "*")
diff --git a/scm/c++.scm b/scm/c++.scm
index a131e7f342..ec54ed28c9 100644
--- a/scm/c++.scm
+++ b/scm/c++.scm
@@ -33,6 +33,11 @@
   (and (pair? x)
        (index? (car x)) (index? (cdr x))))
 
+(define-public (rational-or-procedure? x)
+  (or
+   (and (rational? x) (exact? x))
+   (procedure? x)))
+
 (define-public (number-or-grob? x)
   (or (ly:grob? x) (number? x)))
 
diff --git a/scm/define-context-properties.scm b/scm/define-context-properties.scm
index 073c714097..69262b061d 100644
--- a/scm/define-context-properties.scm
+++ b/scm/define-context-properties.scm
@@ -218,6 +218,17 @@ and @samp{bracketed}.")
 symbol go, measured in half staff spaces from the center of the
 staff.")
      (completionBusy ,boolean? "Whether a completion-note head is playing.")
+     (completionFactor ,rational-or-procedure?
+"When @code{Completion_heads_engraver} and
+@code{Completion_rest_engraver} need to split a note or rest with a
+scaled duration, such as @code{c2*3}, this specifies the scale factor
+to use for the newly-split notes and rests created by the engraver.
+
+If @code{#f}, the completion engraver uses the scale-factor of
+each duration being split.
+
+If set to a callback procedure, that procedure is called with the
+context of the completion engraver, and the duration to be split.")
      (completionUnit ,ly:moment? "Sub-bar unit of completion.")
      (connectArpeggios ,boolean? "If set, connect arpeggios across
 piano staff.")
@@ -447,12 +458,12 @@ associated with the current context.  Ranges from@tie{}@w{-1} to@tie{}1,
 where the values@tie{}@w{-1} (@code{#LEFT}),@tie{}0 (@code{#CENTER})
 and@tie{}1 (@code{#RIGHT}) correspond to hard left, center, and hard
 right, respectively.")
-     (midiReverbLevel ,number? "Reverb effect level for the MIDI channel
-associated with the current context.  Ranges from 0 to@tie{}1
-(0=off,@tie{}1=full effect).")
-     (midiChorusLevel ,number? "Chorus effect level for the MIDI channel
-associated with the current context.  Ranges from 0 to@tie{}1
-(0=off,@tie{}1=full effect).")
+     (midiReverbLevel ,number? "Reverb effect level for the MIDI
+channel associated with the current context.  Ranges from 0
+to@tie{}1 (0=off,@tie{}1=full effect).")
+     (midiChorusLevel ,number? "Chorus effect level for the MIDI
+channel associated with the current context.  Ranges from 0
+to@tie{}1 (0=off,@tie{}1=full effect).")
      (minimumFret ,number? "The tablature auto string-selecting
 mechanism selects the highest string with a fret at least
 @code{minimumFret}.")
diff --git a/scm/lily-library.scm b/scm/lily-library.scm
index 79134ca7d4..570c740775 100644
--- a/scm/lily-library.scm
+++ b/scm/lily-library.scm
@@ -121,6 +121,16 @@ non-visual scale factor 1."
 duration (base note length and dot count), as a number of whole notes."
   (duration-length (duration-visual dur)))
 
+(define-public (unity-if-multimeasure context dur)
+  "Given a context and a duration, return @code{1} if the duration is
+longer than the @code{measureLength} in that context, and @code{#f} otherwise.
+This supports historic use of @code{Completion_heads_engraver} to split
+@code{c1*3} into three whole notes."
+  (if (ly:moment<? (ly:context-property context 'measureLength)
+                   (ly:duration-length dur))
+    1
+    #f))
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; arithmetic
 (define-public (average x . lst)
diff --git a/scm/lily.scm b/scm/lily.scm
index 9e47fc0df7..1d69355e72 100644
--- a/scm/lily.scm
+++ b/scm/lily.scm
@@ -663,6 +663,7 @@ messages into errors.")
     (,number-or-string? . "number or string")
     (,number-pair? . "pair of numbers")
     (,number-pair-list? . "list of number pairs")
+    (,rational-or-procedure? . "an exact rational or procedure")
     (,rhythmic-location? . "rhythmic location")
     (,scheme? . "any type")
     (,string-or-pair? . "string or pair")