>From 0c05e1385ea9658b32b16aafbedfa10a1e7a041e Mon Sep 17 00:00:00 2001 From: Knut Petersen Date: Thu, 22 Dec 2016 01:09:07 +0100 Subject: [PATCH] Add automatic lyric extender generation. English documentation updated. TODO: Translations Signed-off-by: Knut Petersen --- Documentation/learning/common-notation.itely | 13 +- Documentation/notation/vocal.itely | 194 +++++++++++---------------- lily/extender-engraver.cc | 6 - lily/lyric-extender.cc | 53 ++++---- ly/music-functions-init.ly | 54 +++++++- scm/define-grob-properties.scm | 12 ++ scm/define-grobs.scm | 5 +- scm/music-functions.scm | 12 +- 8 files changed, 197 insertions(+), 152 deletions(-) diff --git a/Documentation/learning/common-notation.itely b/Documentation/learning/common-notation.itely index b51766c0a2..4ca4cd8374 100644 --- a/Documentation/learning/common-notation.itely +++ b/Documentation/learning/common-notation.itely @@ -1136,8 +1136,9 @@ that should be included in the melisma: If a syllable extends over several notes or a single very long note an @notation{extender line} is usually drawn from the -syllable extending under all the notes for that syllable. It is -entered as two underscores @code{__}. Here is an example from the +syllable extending under all the notes for that syllable. +Lilypond adds extender lines automatically at appropriate places. +Here is an example from the first three bars of @notation{Dido's Lament}, from Purcell's @notation{Dido and Æneas}: @@ -1151,7 +1152,7 @@ first three bars of @notation{Dido's Lament}, from Purcell's } \addlyrics { When I am laid, - am laid __ in earth, + am laid in earth, } >> @end lilypond @@ -1175,8 +1176,8 @@ far about aligning lyrics to notes. d4 d e | c2 } \addlyrics { - A -- way in a __ man -- ger, - no __ crib for a bed, + A -- way in a man -- ger, + no crib for a bed, } >> @end lilypond @@ -1230,7 +1231,7 @@ example from Handel's @notation{Judas Maccabæus}: c''8 | c8([ bes]) a a([ g]) f | f'4. b, | c4.~ 4 } \addlyrics { - Let flee -- cy flocks the hills a -- dorn, __ + Let flee -- cy flocks the hills a -- dorn, } \relative { \key f \major diff --git a/Documentation/notation/vocal.itely b/Documentation/notation/vocal.itely index 118a58e482..9d5224a4da 100644 --- a/Documentation/notation/vocal.itely +++ b/Documentation/notation/vocal.itely @@ -261,7 +261,7 @@ For more details, see @ref{Automatic syllable durations}. % takes durations and alignment from notes in "one" \new Lyrics \lyricsto "one" { - Life is __ _ love, live __ life. + Life is _ love, live life. } % takes durations and alignment from notes in "one" initially @@ -636,8 +636,8 @@ hyphenated line. This is indicated by placing a double hyphen, Alternatively, when a melisma occurs on the last or only syllable in a word an extender line is usually drawn from the end of the syllable -to the last note of the melisma. This is indicated by placing a -double underscore, @code{__}, immediately after the word. +to the last note of the melisma. Lilypond detects places where extender +lines should be used and adds them automatically. There are five ways in which melismata can be indicated: @@ -656,7 +656,7 @@ together: 8 } \new Lyrics \lyricsto "melody" { - Ky -- ri -- e __ + Ky -- ri -- e } >> @end lilypond @@ -674,7 +674,7 @@ entering lyrics: e8 ( d e2 ) } \new Lyrics \lyricsto "melody" { - Ky -- ri -- e __ + Ky -- ri -- e } >> @end lilypond @@ -736,7 +736,7 @@ to be added to the melisma. e8 d e2 } \new Lyrics \lyricsto "melody" { - Ky -- ri -- _ _ _ e __ _ _ + Ky -- ri -- _ _ _ e _ _ } >> @end lilypond @@ -784,7 +784,7 @@ should be included in the melisma: g8 [ f ] ~ 4 ~ f } \new Lyrics \lyricsto "melody" { - Ky -- ri -- _ e __ _ _ _ + Ky -- ri -- _ e _ _ _ } >> @end lilypond @@ -813,32 +813,9 @@ Notation Reference: Internals Reference: @rinternals{Tunable context properties}. address@hidden -Extender lines under melismata are not created automatically; they -must be inserted manually with a double underscore. - - @node Extenders and hyphens @unnumberedsubsubsec Extenders and hyphens address@hidden melisma address@hidden extender - address@hidden TODO cf Multiple notes to one syllable; should this be merged in? - address@hidden leave this as samp. -gp -In the last syllable of a word, melismata are sometimes indicated with -a long horizontal line starting in the melisma syllable, and ending in -the next one. Such a line is called an extender line, and it is -entered as @samp{ __ } (note the spaces before and after the two -underscore characters). - address@hidden are indicated in the score with extender lines, -which are entered as one double underscore; but short melismata can -also be entered by skipping individual notes, which are entered as -single underscore characters; these do not make an extender line to be -typeset by default.} - @cindex hyphens @c leave this as samp. -gp @@ -854,6 +831,42 @@ distance between two syllables) and the @code{minimum-length} (threshold below which hyphens are removed) properties of @code{LyricHyphen}. address@hidden melisma address@hidden extender + +In the last syllable of a word, melismata are normally indicated with +a long horizontal line starting in the melisma syllable, and ending in +the next one. Such a line is called an extender line, and it is +added automatically. + +Sometimes it is necessary to skip some notes. You can use @samp{ "" } and address@hidden \markup\null } in these cases. + address@hidden + address@hidden instructs lilypond to generate lyric extenders automatically. + address@hidden instructs lilypond not to generate lyric extenders +automatically. + address@hidden #num} sets the lower length limit for lyric extenders +to num staffspaces. No lyric extenders shorter than this will be automatically +generated. If currently inhibited, automatic generation of lyric extenders +is also enabled. + address@hidden #num} generates a lyric extender that starts num staffspaces left +from the associated note instead of a lyrics syllable. + address@hidden forces a lyric extenders where none would be generated otherwise. + address@hidden #num} forces a lyric extenders of length num staffspaces where +none would be generated otherwise. + address@hidden suppresses a lyric extender that would be automatically generated. + address@hidden #num} tells lilypond to stop the next lyric extender num +staffspaces left from the normal endpoint. + @seealso Internals Reference: @rinternals{LyricExtender}, @@ -1284,24 +1297,18 @@ More verses may be added in a similar way: \new Staff { \new Voice = "singleVoice" { \relative { - a'4 a a a - \repeat volta 3 { b4 b b b } + a'4 a a a + \repeat volta 3 { b4 b b b } c4 c c c - } + } } } \new Lyrics \lyricsto "singleVoice" { Not re -- peat -- ed. << { The first time words. } - \new Lyrics { - \set associatedVoice = "singleVoice" - Sec -- ond time words. - } - \new Lyrics { - \set associatedVoice = "singleVoice" - The third time words. - } + \new Lyrics { Sec -- ond time words. } + \new Lyrics { The third time words. } >> The end sec -- tion. } @@ -1324,26 +1331,22 @@ To position them correctly use @code{alignBelowContext}: \new Staff { \new Voice = "melody" { \relative { - a'4 a a a - \repeat volta 3 { b4 b b b } + a'4 a a a + \repeat volta 3 { b4 b b b } c4 c c c - } + } } } \new Lyrics = "firstVerse" \lyricsto "melody" { Not re -- peat -- ed. << { The first time words. } - \new Lyrics = "secondVerse" - \with { alignBelowContext = #"firstVerse" } { - \set associatedVoice = "melody" - Sec -- ond time words. - } - \new Lyrics = "thirdVerse" - \with { alignBelowContext = #"secondVerse" } { - \set associatedVoice = "melody" - The third time words. - } + \new Lyrics = "secondVerse" + \with { alignBelowContext = #"firstVerse" } + { Sec -- ond time words. } + \new Lyrics = "thirdVerse" + \with { alignBelowContext = #"secondVerse" } + { The third time words. } >> The end sec -- tion. } @@ -1396,25 +1399,18 @@ lyrics correctly. } @end lilypond address@hidden \skip @cindex skipping notes in lyrics @cindex lyrics, skipping notes But when the repeated section has different words, or when one of the @code{\alternative} blocks starts with a rest, a repeat -construct cannot be used around the words and @code{\skip} commands +construct cannot be used around the words and @samp{ "" } have to be inserted manually to skip over the notes in the alternative sections which do not apply. Note: do not use an underscore, @code{_}, to skip notes -- an underscore indicates a melisma, causing the preceding syllable -to be left-aligned. - address@hidden @address@hidden command must be followed by a number, -but this number is ignored in lyrics which derive their durations -from the notes in an associated melody through @code{\addlyrics} or address@hidden Each @address@hidden skips a single note of any -value, irrespective of the value of the following number.} +to be left-aligned and an extender to be generated. @lilypond[verbatim,quote,ragged-right] \score { @@ -1431,15 +1427,12 @@ value, irrespective of the value of the following number.} } \new Lyrics { \lyricsto "melody" { - The first time words. - \repeat unfold 2 { \skip 1 } - End here. + The first time words. "" "" End here. } } \new Lyrics { \lyricsto "melody" { - Sec -- ond - \repeat unfold 2 { \skip 1 } + Sec -- ond "" "" time words. } } @@ -1462,8 +1455,13 @@ The tie creates a melisma into the first alternative, but not into the second and subsequent alternatives, so to align the lyrics correctly it is necessary to disable the automatic creation of melismata over the volta section and insert manual skips. +Note the use of @code{\earlyExtender}. @lilypond[quote,verbatim] +\paper { + ragged-right = ##f +} + \score { << \new Staff { @@ -1480,10 +1478,10 @@ melismata over the volta section and insert manual skips. } \new Lyrics { \lyricsto "melody" { - \repeat volta 2 { Here's a __ } + \repeat volta 2 { Here's a } \alternative { - { \skip 1 verse } - { \skip 1 sec } + { _ verse } + { \earlyExtender #-4 sec -- } } ond one. } @@ -1500,40 +1498,15 @@ When the repeated section has different words a @code{\repeat} cannot be used around the lyrics and @code{\skip} commands need to be inserted manually, as before. address@hidden and @code{\earlyExtender} can be used together +with skips coded as @code{""} to give an optimal solution to the problem +of lyric extenders in such situations: + @lilypond[quote,verbatim] -\score { - << - \new Staff { - \time 2/4 - \new Voice = "melody" { - \relative { - \repeat volta 2 { b'4 b ~} - \alternative { { b b } { b \repeatTie c } } - c4 c - } - } - } - \new Lyrics { - \lyricsto "melody" { - Here's a __ verse. - \repeat unfold 2 { \skip 1 } - } - } - \new Lyrics { - \lyricsto "melody" { - Here's one - \repeat unfold 2 { \skip 1 } - more to sing. - } - } - >> +\paper { + ragged-right = ##f } address@hidden lilypond -If you wish to show extenders and hyphens into and out of -alternative sections these must be inserted manually. - address@hidden,verbatim] \score { << \new Staff { @@ -1548,15 +1521,12 @@ alternative sections these must be inserted manually. } \new Lyrics { \lyricsto "melody" { - Here's a __ verse. - \repeat unfold 2 { \skip 1 } + Here's a verse. "" "" } } \new Lyrics { \lyricsto "melody" { - Here's "a_" - \skip 1 - "_" sec -- ond one. + Here's \shortenExtender #4 one "" \earlyExtender #-4 more to sing. } } >> @@ -1667,7 +1637,7 @@ too short, since the lyrics are aligned only to the top voice: @lilypond[quote,verbatim] soprano = \relative { b'8( c d c) d2 } alto = \relative { g'2 b8( a g a) } -words = \lyricmode { la __ la __ } +words = \lyricmode { la la } \new Staff << \new Voice = "sopranoVoice" { \voiceOne \soprano } @@ -1686,7 +1656,7 @@ appropriately: soprano = \relative { b'8( c d c) d2 } alto = \relative { g'2 b8( a g a) } aligner = \relative { b'8( c d c) b( a g a) } -words = \lyricmode { la __ la __ } +words = \lyricmode { la la } \new Staff << \new Voice { \voiceOne \soprano } @@ -1703,7 +1673,7 @@ function, which does not allow lyrics on its own: soprano = \relative { b'8( c d c) d2 } alto = \relative { g'2 b8( a g a) } aligner = \relative { b'8( c d c) b( a g a) } -words = \lyricmode { la __ la __ } +words = \lyricmode { la la } \new Staff << \new Voice \partcombine \soprano \alto @@ -1728,7 +1698,7 @@ soprano = \relative { b'8( c d c) d2 } altoOne = \relative { g'2 b8( a b4) } altoTwo = \relative { d'2 g4( fis8 g) } aligner = \relative { b'8( c d c) d( d d d) } -words = \lyricmode { la __ la __ } +words = \lyricmode { la la } \new ChoirStaff \with {\accepts NullVoice } << \new Staff \soprano @@ -1944,7 +1914,7 @@ block: } \new Lyrics \with { includeGraceNotes = ##t } \lyricsto melody { - Ah __ fa + Ah fa } >> @end lilypond diff --git a/lily/extender-engraver.cc b/lily/extender-engraver.cc index 39fa7788de..c5b9587022 100644 --- a/lily/extender-engraver.cc +++ b/lily/extender-engraver.cc @@ -162,18 +162,12 @@ Extender_engraver::finalize () if (extender_) { completize_extender (extender_); - - if (!extender_->get_bound (RIGHT)) - extender_->warning (_ ("unterminated extender")); extender_ = 0; } if (pending_extender_) { completize_extender (pending_extender_); - - if (!pending_extender_->get_bound (RIGHT)) - pending_extender_->warning (_ ("unterminated extender")); pending_extender_ = 0; } } diff --git a/lily/lyric-extender.cc b/lily/lyric-extender.cc index 8afe2c5569..c1404b6961 100644 --- a/lily/lyric-extender.cc +++ b/lily/lyric-extender.cc @@ -45,51 +45,54 @@ Lyric_extender::print (SCM smob) common = common->common_refpoint (me->get_system (), X_AXIS); Real sl = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness")); + bool start_of_part_b = !left_edge->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface")); + bool end_of_part_a = me->get_bound (RIGHT)->break_status_dir (); + bool force_extender = to_boolean (me->get_property ("force-extender")); + bool no_extender = to_boolean (me->get_property ("no-extender")); - extract_grob_set (me, "heads", heads); + if (!force_extender && no_extender) + return SCM_EOL; - if (!heads.size ()) + extract_grob_set (me, "heads", heads); + if (!heads.size () || (!force_extender && !start_of_part_b && !end_of_part_a && heads.size () < 2)) return SCM_EOL; common = common_refpoint_of_array (heads, common, X_AXIS); Real left_point = 0.0; - if (left_edge->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface"))) + if (!start_of_part_b) left_point = left_edge->extent (common, X_AXIS)[RIGHT]; - else if (heads.size ()) - left_point = heads[0]->extent (common, X_AXIS)[LEFT]; else - left_point = left_edge->extent (common, X_AXIS)[RIGHT]; + left_point = heads[0]->extent (common, X_AXIS)[LEFT]; if (isinf (left_point)) return SCM_EOL; - /* It seems that short extenders are even lengthened to go past the - note head, but haven't found a pattern in it yet. --hwn 1/1/04 */ - SCM minlen = me->get_property ("minimum-length"); - Real right_point - = left_point + (robust_scm2double (minlen, 0)); - - right_point = min (right_point, me->get_system ()->get_bound (RIGHT)->relative_coordinate (common, X_AXIS)); - - if (heads.size ()) - right_point = max (right_point, heads.back ()->extent (common, X_AXIS)[RIGHT]); - Real h = sl * robust_scm2double (me->get_property ("thickness"), 0); Drul_array paddings (robust_scm2double (me->get_property ("left-padding"), h), robust_scm2double (me->get_property ("right-padding"), h)); + Real force_len = robust_scm2double (me->get_property ("force-length"), 0); + Real collapse_len = robust_scm2double (me->get_property ("collapse-length"), 0); + + Real right_point = heads.back ()->extent (common, X_AXIS)[RIGHT]; + + if (force_extender) + right_point = max (heads.back ()->extent (common, X_AXIS)[RIGHT], left_point + force_len); if (right_text) - right_point = min (right_point, (robust_relative_extent (right_text, common, X_AXIS)[LEFT] - paddings[RIGHT])); + right_point = min (right_point, (robust_relative_extent (right_text, common, X_AXIS)[LEFT])); + + if (end_of_part_a) + right_point = robust_relative_extent (me->get_bound (RIGHT), common, X_AXIS)[LEFT]; + + right_point -= paddings[RIGHT]; - /* run to end of line. */ - if (me->get_bound (RIGHT)->break_status_dir ()) - right_point = max (right_point, (robust_relative_extent (me->get_bound (RIGHT), common, X_AXIS)[LEFT] - paddings[RIGHT])); + if (!start_of_part_b) + left_point += paddings[LEFT]; - left_point += paddings[LEFT]; Real w = right_point - left_point; - if (w < 1.5 * h) + if (w < collapse_len && !force_extender && !end_of_part_a && !start_of_part_b) return SCM_EOL; Stencil mol (Lookup::round_filled_box (Box (Interval (0, w), @@ -106,9 +109,13 @@ ADD_INTERFACE (Lyric_extender, " note).", /* properties */ + "collapse-length " + "force-extender " + "force-length " "heads " "left-padding " "next " + "no-extender " "right-padding " "thickness " ); diff --git a/ly/music-functions-init.ly b/ly/music-functions-init.ly index 1f94319bce..babfaed3e2 100644 --- a/ly/music-functions-init.ly +++ b/ly/music-functions-init.ly @@ -202,6 +202,29 @@ staves.") (context-spec-music (make-skip-music skip) 'Staff "down" clef-2))))) +autoExtenderLimit = +#(define-music-function (num) (number?) + (_i "Set the lower length limit for lyric extenders +to @var{num} staffspaces. No lyric extenders shorter than +this will be automatically generated. If currently inhibited, +automatic generation of lyric extenders is also enabled.") + #{ \autoExtendersOn + \override Lyrics.LyricExtender.collapse-length = #num #}) + +autoExtendersOff = +#(define-music-function () () + (_i "Instructs lilypond not to generate lyric extenders +automatically.") + #{ \override LyricExtender.no-extender = ##t + \override LyricExtender.force-extender = ##f #}) + +autoExtendersOn = +#(define-music-function () () + (_i "Instructs lilypond to generate lyric extenders +automatically.") + #{ \override LyricExtender.no-extender = ##f + \override LyricExtender.force-extender = ##f #}) + balloonGrobText = #(define-music-function (grob-name offset text) (symbol? number-pair? markup?) @@ -394,7 +417,13 @@ displayScheme = (display-scheme-music expr port)) expr) - +earlyExtender = +#(define-music-function (num) (number?) + (_i "Generate a lyric extender that starts @var{num} +staffspaces left from the associated note instead of +a lyrics syllable.") + #{ \forceExtender + \lyricmode { \tweak self-alignment-X #num \markup\null } #}) endSpanners = #(define-music-function (music) (ly:music?) @@ -495,6 +524,17 @@ to the preceding note or rest as a post-event with @code{-}.") 'footnote-text footnote))) (once (propertyTweak 'footnote-music mus item)))) +forceExtender = +#(define-music-function () () + (_i "Force a lyric extender once.") + #{ \once \override LyricExtender.force-extender = ##t #}) + +forceExtenderTo = +#(define-music-function (num) (number?) + (_i "Force a lyric extender of length @var{num} staffspaces.") + #{ \forceExtender + \once \override Lyrics.LyricExtender.force-length = #num #}) + grace = #(def-grace-function startGraceMusic stopGraceMusic (_i "Insert @var{music} as grace notes.")) @@ -879,6 +919,12 @@ musicMap = (_i "Apply @var{proc} to @var{mus} and all of the music it contains.") (music-map proc mus)) +noExtender = +#(define-music-function () () + (_i "Suppress a lyric extender that would be automatically +generated.") + #{ \once \override LyricExtender.no-extender = ##t #}) + %% noPageBreak and noPageTurn are music functions (not music indentifiers), %% because music identifiers are not allowed at top-level. noPageBreak = @@ -1680,6 +1726,12 @@ shiftDurations = (shift-duration-log arg dur dots)) +shortenExtender = +#(define-music-function (num) (number?) + (_i "Stop the next lyric extender @var{num} staffspaces +left from the normal endpoint.") + #{ \once \override LyricExtender.right-padding = #num #}) + single = #(define-music-function (overrides music) (ly:music? ly:music?) diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index c234792dd2..41b6139313 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -185,6 +185,9 @@ that should be used for clef modifiers with various clefs") edges of beams?") (collapse-height ,ly:dimension? "Minimum height of system start delimiter. If equal or smaller, the bracket/@/brace/@/line is removed.") + (collapse-length ,ly:dimension? "An automatically generated +lyric extender is suppressed if it would be shorter than +this length.") (collision-interfaces ,list? "A list of interfaces for which automatic beam-collision resolution is run.") (collision-voice-only ,boolean? "Does automatic beam collsion apply @@ -330,9 +333,16 @@ allowed.") (footnote ,boolean? "Should this be a footnote or in-note?") (footnote-music ,ly:music? "Music creating a footnote.") (footnote-text ,markup? "A footnote for the grob.") + (force-extender ,boolean? "Force a lyric extender to be generated +if none would be generated otherwise and/or force it to be at least as +wide as indicated by property @code{length-limit-or-forced-length} unless +it would collide with the next syllable. Overrides property address@hidden") (force-hshift ,number? "This specifies a manual shift for notes in collisions. The unit is the note head width of the first voice note. This is used by @rinternals{note-collision-interface}.") + (force-length ,ly:dimension? "A forced lyric extender +is given this length if possible.") (forced-spacing ,number? "Spacing forced between grobs, used in various ligature engravers.") (fraction ,fraction? "Numerator and denominator of a time @@ -663,6 +673,8 @@ syllable following an extender).") (no-alignment ,boolean? "If set, don't place this grob in a @code{VerticalAlignment}; rather, place it using its own @code{Y-offset} callback.") + (no-extender ,boolean? "Inhibits generation of lyric extenders. +Can be overridden by property @code{force-extender}.") (no-ledgers ,boolean? "If set, don't draw ledger lines on this object.") (no-stem-extend ,boolean? "If set, notes with ledger lines do not diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm index e36ea12bf2..714aff5b1a 100644 --- a/scm/define-grobs.scm +++ b/scm/define-grobs.scm @@ -1361,7 +1361,10 @@ (LyricExtender . ( - (minimum-length . 1.5) + (collapse-length . 1.5) + (force-extender . #f) + (force-length . 1.5) + (no-extender . #f) (stencil . ,ly:lyric-extender::print) (thickness . 0.8) ; line-thickness (Y-extent . (0 . 0)) diff --git a/scm/music-functions.scm b/scm/music-functions.scm index b1dc2f9c61..8cb9cf7ac5 100644 --- a/scm/music-functions.scm +++ b/scm/music-functions.scm @@ -728,10 +728,16 @@ making it possible to @code{\\revert} to any previous value afterwards." 'articulation-type name properties)) +(define (add-extender! event) + (ly:music-set-property! event 'articulations + (append (ly:music-property event 'articulations) + (list (make-music (quote ExtenderEvent))))) + event) + (define-public (make-lyric-event string duration) - (make-music 'LyricEvent - 'duration duration - 'text string)) + (if (and (string? string)(string=? " " string)) + (make-music 'LyricEvent 'duration duration 'text string) + (add-extender! (make-music 'LyricEvent 'duration duration 'text string)))) (define-safe-public (make-span-event type span-dir) (make-music type -- 2.11.0