* Iterator tutorial::
* Engraver tutorial::
* Callback tutorial::
+* Understanding pure properties::
* LilyPond scoping::
* Scheme->C interface::
* LilyPond miscellany::
TODO -- This is a placeholder for a tutorial on callback functions.
+
+@node Understanding pure properties
+@section Understanding pure properties
+
+@menu
+* Purity in LilyPond::
+* Writing a pure function::
+* How purity is defined and stored::
+* Where purity is used::
+* Case studies::
+* Debugging tips::
+@end menu
+
+Pure properties are some of the most difficult properties to understand
+in LilyPond but, once understood, it is much easier to work with
+horizontal spacing. This document provides an overview of what it means
+for something to be @q{pure} in LilyPond, what this purity guarantees,
+and where pure properties are stored and used. It finishes by
+discussing a few case studies for the pure programmer to save you some
+time and to prevent you some major headaches.
+
+
+@node Purity in LilyPond
+@subsection Purity in LilyPond
+Pure properties in LilyPond that do not have any @q{side effects}.
+That is, looking up a pure property should never result in calls to the
+following functions:
+@itemize
+@item @code{set_property}
+@item @code{set_object}
+@item @code{suicide}
+@end itemize
+This means that, if the property is calculated via a callback, this callback
+must not only avoid the functions above but make sure that any functions
+it calls also avoid the functions above. Also, to date in LilyPond, a pure
+function will always return the same value before line breaking (or, more
+precisely, before any version of @code{break_into_pieces} is called). This
+convention makes it possible to cache pure functions and be more flexible
+about the order in which functions are called. For example; Stem #'length has
+a pure property that will @emph{never} trigger one of the functions listed
+above and will @emph{always} return the same value before line breaking,
+independent of where it is called. Sometimes, this will be the actual length
+of the Stem. But sometimes it will not. For example; stem that links up
+with a beam will need its end set to the Y position of the beam at the stem's
+X position. However, the beam's Y positions can only be known after the score
+is broken up in to several systems (a beam that has a shallow slope on a
+compressed line of music, for example, may have a steeper one on an
+uncompressed line). Thus, we only call the impure version of the properties
+once we are @emph{absolutely certain} that all of the parameters needed to
+calculate their final value have been calculated. The pure version provides a
+useful estimate of what this Stem length (or any property) will be, and
+the art of creating good pure properties is trying to get the estimation
+as close to the actual value as possible.
+
+Of course, like Gregory Peck and Tintin, some Grobs will have properties
+that will always be pure. For example, the height of a note-head in
+not-crazy music will never depend on line breaking or other parameters
+decided late in the typesetting process. Inversely, in rare cases,
+certain properties are difficult to estimate with pure values. For
+example, the height of a Hairpin at a certain cross-section of its
+horizontal span is difficult to know without knowing the horizontal
+distance that the hairpin spans, and LilyPond provides an
+over-estimation by reporting the pure height as the entire height of the
+Hairpin.
+
+Purity, like for those living in a convent, is more like a contract than
+an @emph{a priori}. If you write a pure-function, you are promising
+the user (and the developer who may have to clean up after you) that
+your function will not be dependent on factors that change at different
+stages of the compilation process (compilation of a score, not of
+LilyPond).
+
+One last oddity is that purity, in LilyPond, is currently limited
+exclusively to things that have to do with Y-extent and positioning.
+There is no concept of @q{pure X} as, by design, X is always the
+independent variable (i.e. from column X1 to column X2, what will be the
+Y height of a given grob). Furthermore, there is no purity for
+properties like color, text, and other things for which a meaningful notion
+of estimation is either not necessary or has not yet been found. For example,
+even if a color were susceptible to change at different points of the
+compilation process, it is not clear what a pure estimate of this color
+would be or how this pure color could be used. Thus, in this document and
+in the source, you will see purity discussed almost interchangeably with
+Y-axis positioning issues.
+
+
+@node Writing a pure function
+@subsection Writing a pure function
+Pure functions take, at a minimum, three arguments: the @var{grob}, the
+starting column at which the function is being evaluated (hereafter
+referred to as @var{start}), and the end column at which the grob is
+being evaluated (hereafter referred to as @var{end}). For items,
+@var{start} and @var{end} must be provided (meaning they are not optional)
+but will not have a meaningful impact on the result, as items only occupy
+one column and will thus yield a value or not (if they are not in the range
+from @var{start} to @var{end}). For spanners however, @var{start} and
+@var{end} are important, as we may can get a better pure estimation of a
+slice of the spanner than considering it on the whole. This is useful
+during line breaking, for example, when we want to estimate the Y-extent
+of a spanner broken at given starting and ending columns.
+
+If the pure function you're writing takes more than three arguments
+(say, for example, a chained offset callback), this is not a problem:
+just make sure that the grob is the first argument and that start and
+end are the last two arguments.
+
+
+@node How purity is defined and stored
+@subsection How purity is defined and stored
+Purity can currently be defined two different ways in LilyPond that
+correspond to two types of scenarios. In one scenario, we know that a
+callback is pure, but we are not necessarily certain what properties
+will use this callback. In another, we want a property to be pure, but
+we don't want to guarantee that its callback function will be pure in
+all circumstances.
+
+In the first scenario, we register the callback in define-grobs.scm in
+one of four places depending on what the function does.
+
+@itemize
+@item @code{pure-print-functions}: If finding a print function's vertical
+extent does not have any @q{side effects} we register it here. We then
+don't have to set the pure Y-extent property, which will be taken from the
+stencil.
+
+@item @code{pure-print-to-height-conversions}: If a stencil can
+eventually be used to glean a grob's Y-extent but is not pure (meaning
+it will have a different height at different stages of the compilation
+process), we add it to this list along with a function for the pure
+Y-extent.
+
+@item @code{pure-conversions-alist}: This list contains pairs of
+functions and their pure equivalents. It is onto but not one-to-one.
+
+@item @code{pure-functions}: Like pure-print-functions in that they work
+for both pure and impure values, but they do not return a stencil.
+@end itemize
+
+At all stages of the compilation process, when LilyPond wants the pure
+version of a property, it will consult these lists and see if it can get
+this property for a given Grob. Note that you do @emph{not} need to
+register the pure property in the grob itself. For example, there is no
+property @q{pure-Y-extent}. Rather, by registering these functions as
+defined above, every time LilyPond needs a pure property, it will check
+to see if a Grob contains one of these functions and, if so, will use
+its value. If LilyPond cannot get a pure function, it will return a
+value of @code{##f} for the property.
+
+LilyPond is smart enough to know if a series of chained functions are
+pure. For example, if a Y-offset property has four chained functions
+and all of them have pure equivalents, LilyPond will read the four pure
+equivalents when calculating the pure property. However, if even one is
+impure, LilyPond will not return a pure property for the offset (instead
+returning something like @code{#f} or @code{'()}) and will likely wreak
+havoc on your score.
+
+In the second scenario, we create an unpure-pure-container (unpure is
+not a word, but hey, neither was Lilypond until the 90s). For example:
+
+@example
+#(define (foo grob)
+ '(-1 . 1))
+
+#(define (bar grob start end)
+ '(-2 . 2))
+
+\override Stem #'length = #(ly:make-unpure-pure-container foo bar)
+@end example
+
+This is useful if we want to:
+
+@itemize
+@item create overrides that have pure alternatives (should not be used
+in development, but useful for users)
+
+@item use return values that are not functions (i.e. pairs or booleans)
+for either pure or unpure values.
+
+@item allow a function to be considered pure in a limited amount of
+circumstances. This is useful if we are sure that, when associated with
+one grob a function will be pure but not necessarily with another grob
+that has different callbacks.
+@end itemize
+
+Items can only ever have two pure heights: their actual pure height if
+they are between @q{start} and @q{end}, or an empty interval if they are
+not. Thus, their pure property is cached to speed LilyPond up. Pure
+heights for spanners are generally not cached as they change depending
+on the start and end values. They are only cached in certain particular
+cases. Before writing a lot of caching code, make sure that it is a
+value that will be reused a lot.
+
+
+@node Where purity is used
+@subsection Where purity is used
+Pure Y values must be used in any functions that are called before
+line breaking. Examples of this can be seen in
+@code{Separation_items::boxes} to construct horizontal skylines and in
+@code{Note_spacing::stem_dir_correction} to correct for optical
+illusions in spacing. Pure properties are also used in the calculation
+of other pure properties. For example, the @code{Axis_group_interface}
+has pure functions that look up other pure functions.
+
+Purity is also implicitly used in any functions that should only ever
+return pure values. For example, extra-spacing-height is only ever used
+before line-breaking and thus should never use values that would only be
+available after line breaking. In this case, there is no need to create
+callbacks with pure equivalents because these functions, by design, need
+to be pure.
+
+To know if a property will be called before and/or after line-breaking
+is sometimes tricky and can, like all things in coding, be found by
+using a debugger and/or adding @var{printf} statements to see where they
+are called in various circumstances.
+
+
+@node Case studies
+@subsection Case studies
+In each of these case studies, we expose a problem in pure properties, a
+solution, and the pros and cons of this solution.
+
+@subheading Time signatures
+A time signature needs to prevent accidentals from passing over or under
+it, but its extent does not necessarily extend to the Y-position of
+accidentals. LilyPond's horizontal spacing sometimes makes a line of
+music compact and, when doing so, allows certain columns to pass over
+each other if they will not collide. This type of passing over is not
+desirable with time signatures in traditional engraving. But how do we
+know if this passing over will happen before line breaking, as we are
+not sure what the X positions will be? We need a pure estimation of how
+much extra spacing height the time signatures would need to prevent this
+form of passing over without making this height so large as to
+overly-distort the Y-extent of an system, which could result in a very
+@q{loose} looking score with lots of horizontal space between columns.
+So, to approximate this extra spacing height, we use the Y-extent of a
+time signature's next-door-neighbor grobs via the pure-from-neighbor
+interface.
+
+@itemize
+@item pros: By extending the extra spacing height of a time signature to
+that of its next-door-neighbors, we make sure that grobs to the right of
+it that could pass above or below it do not.
+
+@item cons: This over-estimation of the vertical height could prevent
+snug vertical spacing of systems, as the system will be registered as
+being taller at the point of the time signature than it actually is.
+This approach can be used for clefs and bar lines as well.
+@end itemize
+
+@subheading Stems
+As described above, Stems need pure height approximations when they are
+beamed, as we do not know the beam positions before line breaking. To
+estimate this pure height, we take all the stems in a beam and find
+their pure heights as if they were not beamed. Then, we find the union
+of all these pure heights and take the intersection between this
+interval (which is large) and an interval going from the note-head of a
+stem to infinity in the direction of the stem so that the interval stops
+at the note head.
+
+@itemize
+@item pros: This is guaranteed to be at least as long as the beamed
+stem, as a beamed stem will never go over the ideal length of the
+extremal beam of a stem.
+
+@item cons: Certain stems will be estimated as being too long, which
+leads to the same problem of too-much-vertical-height as described
+above.
+
+@end itemize
+
+
+@node Debugging tips
+@subsection Debugging tips
+A few questions to ask yourself when working with pure properties:
+
+@itemize
+@item Is the property really pure? Are you sure that its value could
+not be changed later in the compiling process due to other changes?
+
+@item Can the property be made to correspond even more exactly with the
+eventual impure property?
+
+@item For a spanner, is the pure property changing correctly depending
+on the starting and ending points of the spanner?
+
+@item For an Item, will the item's pure height need to act in horizontal
+spacing but not in vertical spacing? If so, use extra-spacing-height
+instead of pure height.
+
+@end itemize
+
+
@node LilyPond scoping
@section LilyPond scoping
-The Lilypond language has a concept of scoping, i.e. you can do
+The Lilypond language has a concept of scoping, i.e. you can do:
@example
foo = 1
used as the return value of the scheme function. It may contain
LilyPond code blocks enclosed in hashed braces
(@tie{}@w{@code{#@{@dots{}#@}}}@tie{}), like described in @ref{Lilypond
-code blocks}. Within LilyPond code blocks, use @code{$} to reference
-function arguments (eg., @samp{$arg1}) or to start an inline Scheme
-expression containing function arguments (eg., @w{@samp{$(cons arg1
-arg2)}}). If your function returns a music expression, it is cloned and
-given the correct @code{origin}.
+code blocks}. Within LilyPond code blocks, use @code{#} to reference
+function arguments (eg., @samp{#arg1}) or to start an inline Scheme
+expression containing function arguments (eg., @w{@samp{#(cons arg1
+arg2)}}). Where normal Scheme expressions using @code{#} don't do the
+trick, you might need to revert to immediate Scheme expressions using
+@code{$}, for example as @samp{$music}.
+
+If your function returns a music expression, it is cloned and given the
+correct @code{origin}.
@end multitable
@noindent
@code{ly:pitch?} and @code{ly:duration?}.
Suitability of arguments for all other predicates is determined by
-actually calling the predicate after Lilypond has already converted
-them into a Scheme expression. As a consequence, the argument can be
+actually calling the predicate after Lilypond has already converted them
+into a Scheme expression. As a consequence, the argument can be
specified in Scheme syntax if desired (introduced with @code{#} or as
-the result of calling a scheme function), but Lilypond will also
-convert a number of Lilypond constructs into Scheme before actually
-checking the predicate on them. Currently, those include music,
-simple strings (with or without quotes), numbers, full markups and markup
+the result of calling a scheme function), but Lilypond will also convert
+a number of Lilypond constructs into Scheme before actually checking the
+predicate on them. Currently, those include music, postevents, simple
+strings (with or without quotes), numbers, full markups and markup
lists, score, book, bookpart, context definition and output definition
blocks.
able to ``backup'' when it decides the expression does not fit the
parameter. So some forms of music might need to be enclosed in braces
to make them acceptable to Lilypond. There are also some other
-complications that may cause a predicate function to be called several
-times on successive versions of an argument (like @code{3} and
-@code{3\cm}) or several interpretations (like @code{"a" 4} in lyric
-mode, which can either be a string followed by a number, or a lyric
-event of duration @code{4}).
-
-Music arguments preceding @code{ly:duration?} arguments must also be
-lookahead-free. This may also hold for the last argument of a scheme
-function that is used as the last part of another expression, since
-otherwise Lilypond won't know whether following postevents or
-durations apply to the argument of the Scheme function, or to the
-containing music expression.
-
-For a list of available type predicates, see
+ambiguities that Lilypond sorts out by checking with predicate
+functions: is @samp{-3} a fingering postevent or a negative number? Is
+@code{"a" 4} in lyric mode a string followed by a number, or a lyric
+event of duration @code{4}? Lilypond decides by asking the predicates.
+That means that a lenient predicate like @code{scheme?} might be good
+for surprising interpretations.
+
+For a list of available predefined type predicates, see
@ruser{Predefined type predicates}.
@seealso
Scheme functions can be called pretty much anywhere where a Scheme
expression starting with @code{#} can be written. You call a scheme
function by writing its name preceded by @code{\}, followed by its
-arguments. The last argument can't be an optional argument. If there
-are several optional arguments in a row, they are filled with values
-left to right. Once an optional argument can't match input, it and all
-immediately following optional arguments are replaced with their default
-values, and the matching continues with the next non-optional argument.
+arguments. Once an optional argument predicate does not match an
+argument, Lilypond skips this and all following optional arguments,
+replacing them with their specified default, and @q{backs up} the
+argument that did not match to the place of the next mandatory argument.
+Since the backed up argument needs to go somewhere, optional arguments
+are not actually considered optional unless followed by a mandatory
+argument.
+
+There is one exception: if you write @code{\default} in the place of an
+optional argument, this and all following optional arguments are skipped
+and replaced by their default. This works even when no mandatory
+argument follows since @code{\default} does not need to get backed up.
+The @code{mark} and @code{key} commands make use of that trick to
+provide their default behavior when just followed by @code{\default}.
Apart from places where a Scheme value is required, there are a few
places where @code{#} expressions are currently accepted and evaluated
(parser location beg-end)
(pair?)
#@{
- \once \override Beam #'positions = $beg-end
+ \once \override Beam #'positions = #beg-end
#@})
\relative c' @{
(parser location beg end)
(number? number?)
#{
- \once \override Beam #'positions = $(cons beg end)
+ \once \override Beam #'positions = #(cons beg end)
#})
\relative c' {
(parser location mag)
(number?)
#{
- \override Stem #'length = $(* 7.0 mag)
+ \override Stem #'length = #(* 7.0 mag)
\override NoteHead #'font-size =
- $(inexact->exact (* (/ 6.0 (log 2.0)) (log mag)))
+ #(inexact->exact (* (/ 6.0 (log 2.0)) (log mag)))
#})
AltOff = {
(parser location mag music)
(number? ly:music?)
#{
- \override Stem #'length = $(* 7.0 mag)
+ \override Stem #'length = #(* 7.0 mag)
\override NoteHead #'font-size =
- $(inexact->exact (* (/ 6.0 (log 2.0)) (log mag)))
+ #(inexact->exact (* (/ 6.0 (log 2.0)) (log mag)))
$music
\revert Stem #'length
\revert NoteHead #'font-size
-\version "2.14.0"
+\version "2.15.17"
\header {
- texidoc = "When a beam goes over a rest, there should not be any
-beamlets pointing towards the rest unless absolutely necessary."
+ texidoc = "When a beam goes over a rest, beamlets should be as necessary
+ to show the beat structure."
}
\relative c' {
c8[ r16 c32 c32]
c32[ r16 c32 c8]
- c32[ r16 c64 c8 c64]
+ c32[ r16 c64 c64 ~ c16.. c64]
c32[ c32 r16 c8]
c16[ r32 c32 r16 c16]
c16[ r16 c32 r32 c16]
--- /dev/null
+\version "2.15.20"
+
+\header {
+ texidoc = "
+Beamlets should point in the direction of the beat to which they
+belong.
+"
+}
+
+
+\relative c' {
+b16. b32 b32 b16.
+b16.[ b32 b b b b16.]
+}
--- /dev/null
+\version "2.15.20"
+
+\header {
+ texidoc = "
+Beamlets should point away from complete beat units and toward off-beat or
+broken beat units. This should work in tuplets as well as in ordinary time.
+"
+}
+
+\relative c'' {
+ \times 2/3 {
+ c8. c16 c8
+ }
+ \times 2/3 {
+ c8 c16 c8.
+ }
+ \times 4/5 {
+ c8[ c8. c16 c8 c8]
+ }
+ \times 4/5 {
+ c8[ c8 c16 c8. c8]
+ }
+ \times 4/5 {
+ c8 c16 c8. c8 c8
+ }
+ \times 4/5 {
+ c8 c8 c8. c16 c8
+ }
+ c8.[ c16 c8 c8]
+ c8[ c16 c8. c8]
+ c8[ c8. c16 c8]
+ c8.[ c16 c8. c16]
+ \times 4/5 { c8 [ c16 c8 c16 c8 c8 ] }
+ \times 4/5 { a8 a32 a8 a16. a8 a8 }
+}
+
\header {
- texidoc = "SpanBars participate in the horizontal collision system;
-the accidentals should not collide with the bar lines."
+ texidoc = "Because @code{BarLine} grobs take their
+extra-positioning-height from their neighbors via the
+@code{pure-from-neighbor-interface}, the left edge of an
+accidental should never fall to the left of the right
+edge of a bar line. This spacing should also take place when
+@code{SpanBar} grobs are present.
+"
}
-\version "2.14.0"
+\version "2.15.21"
upper = \relative c' {
\key f \minor \time 12/8
if (bool (beam_start_location_.grace_part_) != bool (now.grace_part_))
return;
- Moment dur = unsmob_duration (ev->get_property ("duration"))->get_length ();
+ Duration *stem_duration = unsmob_duration (ev->get_property ("duration"));
+ Moment dur = stem_duration->get_length ();
+
+ //Moment dur = unsmob_duration (ev->get_property ("duration"))->get_length ();
Moment measure_now = measure_position (context ());
bool recheck_needed = false;
grouping_->add_stem (now - beam_start_moment_ + beam_start_location_,
durlog - 2,
- Stem::is_invisible (stem));
+ Stem::is_invisible (stem),
+ stem_duration->factor ());
stems_->push_back (stem);
last_add_mom_ = now;
extend_mom_ = max (extend_mom_, now) + get_event_length (ev, now);
Axis_group_interface::calc_pure_relevant_grobs (SCM smob)
{
Grob *me = unsmob_grob (smob);
+ return internal_calc_pure_relevant_grobs (me, "elements");
+}
- extract_grob_set (me, "elements", elts);
+SCM
+Axis_group_interface::internal_calc_pure_relevant_grobs (Grob *me, string grob_set_name)
+{
+ extract_grob_set (me, grob_set_name.c_str (), elts);
vector<Grob *> relevant_grobs;
SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
"kern "
"thin-kern "
"hair-thickness "
+ "has-span-bar "
"thick-thickness "
"glyph "
"glyph-name "
}
last_stem_added_at_ = now;
- int durlog = unsmob_duration (ev->get_property ("duration"))->duration_log ();
+
+ Duration *stem_duration = unsmob_duration (ev->get_property ("duration"));
+ int durlog = stem_duration->duration_log ();
+ //int durlog = unsmob_duration (ev->get_property ("duration"))->duration_log ();
if (durlog <= 2)
{
ev->origin ()->warning (_ ("stem does not fit in beam"));
Moment stem_location = now - beam_start_mom_ + beam_start_location_;
beam_info_->add_stem (stem_location,
max (durlog - 2, 0),
- Stem::is_invisible (stem));
+ Stem::is_invisible (stem),
+ stem_duration->factor ());
Beam::add_stem (beam_, stem);
}
beam_count_drul_[LEFT] = 0;
beam_count_drul_[RIGHT] = 0;
invisible_ = false;
+ factor_ = Rational (1);
}
-Beam_rhythmic_element::Beam_rhythmic_element (Moment m, int i, bool inv)
+Beam_rhythmic_element::Beam_rhythmic_element (Moment m, int i, bool inv, Rational factor)
{
start_moment_ = m;
rhythmic_importance_ = 0;
beam_count_drul_[LEFT] = i;
beam_count_drul_[RIGHT] = i;
invisible_ = inv;
+ factor_ = factor;
}
void
if (count <= left_count && count <= right_count)
return CENTER;
- // Try to avoid sticking-out flags as much as possible by pointing my flags
- // at the neighbour with the most flags.
- else if (right_count > left_count)
- return RIGHT;
- else if (left_count > right_count)
- return LEFT;
-
// If all else fails, point the beamlet away from the important moment.
- return (infos_[i].rhythmic_importance_ <= infos_[i + 1].rhythmic_importance_) ? RIGHT : LEFT;
+ return (infos_[i].rhythmic_importance_ <= infos_[i + 1].rhythmic_importance_)
+ ? RIGHT : LEFT;
}
void
find_rhythmic_importance (options);
+ vector <Direction> flag_directions;
+ // Get the initial flag directions
+ for (vsize i = 0; i < infos_.size (); i++)
+ flag_directions.push_back (flag_direction (options, i));
+
+ // Correct flag directions for subdivision
for (vsize i = 1; i < infos_.size () - 1; i++)
{
- Direction non_flag_dir = other_dir (flag_direction (options, i));
- if (non_flag_dir)
+ if ((flag_directions[i] == CENTER) && (flag_directions[i - 1] == LEFT))
+ flag_directions[i] = RIGHT;
+ if ((flag_directions[i] == CENTER) && (flag_directions[i + 1] == RIGHT))
+ flag_directions[i] = LEFT;
+ }
+
+ // Set the count on each side of the stem
+ // We need to run this code twice to make both the
+ // left and the right counts work properly
+ for (int i = 0; i < 2; i++)
+ for (vsize i = 1; i < infos_.size () - 1; i++)
+ {
+ Direction non_flag_dir = other_dir (flag_directions[i]);
+ if (non_flag_dir)
+ {
+ int importance = infos_[i + 1].rhythmic_importance_;
+ int count = (importance < 0 && options.subdivide_beams_)
+ ? 1 : min (min (infos_[i].count (non_flag_dir),
+ infos_[i + non_flag_dir].count (-non_flag_dir)),
+ infos_[i - non_flag_dir].count (non_flag_dir));
+
+ infos_[i].beam_count_drul_[non_flag_dir] = count;
+ }
+ }
+}
+
+/*
+ Get the group start position, the next group starting position, and the
+ next beat starting position, given start_moment, base_moment,
+ grouping, and factor
+*/
+void
+find_location (SCM grouping, Moment base_moment, Moment start_moment,
+ Rational factor, Moment *group_pos, Moment *next_group_pos,
+ Moment *next_beat_pos)
+{
+ *group_pos = Moment (0);
+ *next_group_pos = Moment (0);
+ *next_beat_pos = base_moment;
+
+ while (*next_beat_pos <= start_moment)
+ *next_beat_pos += base_moment;
+
+ while (*next_group_pos < *next_beat_pos)
+ {
+ int count = 1; //default -- 1 base moments in a beam
+ if (scm_is_pair (grouping))
{
- int importance = (non_flag_dir == LEFT)
- ? infos_[i].rhythmic_importance_ : infos_[i + 1].rhythmic_importance_;
- int count = (importance < 0 && options.subdivide_beams_)
- ? 1 : min (infos_[i].count (non_flag_dir),
- infos_[i + non_flag_dir].count (-non_flag_dir));
+ count = scm_to_int (scm_car (grouping));
+ grouping = scm_cdr (grouping);
+ }
- infos_[i].beam_count_drul_[non_flag_dir] = count;
+ // If we have a tuplet, the count should be determined from
+ // the maximum tuplet size for beamed tuplets.
+ int tuplet_count = factor.num ();
+ if (tuplet_count > 1)
+ {
+ // We use 1/8 as the base moment for the tuplet because it's
+ // the largest beamed value. If the tuplet is shorter, it's
+ // OK, the code still works
+ int test_count = (tuplet_count * Moment (Rational (1, 8)) / base_moment).num ();
+ if (test_count > count) count = test_count;
}
+ *group_pos = *next_group_pos;
+ *next_group_pos = *group_pos + count * base_moment;
}
}
void
Beaming_pattern::find_rhythmic_importance (Beaming_options const &options)
{
- Moment measure_pos (0);
+ Moment group_pos (0); // 0 is the start of the first group
+ Moment next_group_pos (0);
+ Moment next_beat_pos (options.base_moment_);
+ int tuplet_count = 1;
+
SCM grouping = options.grouping_;
vsize i = 0;
+ // Find where we are in the beat structure of the measure
+ if (infos_.size ())
+ find_location (grouping, options.base_moment_, infos_[i].start_moment_,
+ infos_[i].factor_, &group_pos, &next_group_pos, &next_beat_pos);
+
// Mark the importance of stems that start at a beat or a beat group.
while (i < infos_.size ())
{
- // If a beat grouping is not specified, default to 2 beats per group.
- int count = 2;
- if (scm_is_pair (grouping))
- {
- count = scm_to_int (scm_car (grouping));
- grouping = scm_cdr (grouping);
- }
-
+ tuplet_count = infos_[i].factor_.den ();
+ if ((next_beat_pos > next_group_pos)
+ || (infos_[i].start_moment_ > next_beat_pos))
+ // Find the new group ending point
+ find_location (grouping, options.base_moment_, infos_[i].start_moment_,
+ infos_[i].factor_, &group_pos, &next_group_pos, &next_beat_pos);
// Mark the start of this beat group
- if (infos_[i].start_moment_ == measure_pos)
+ if (infos_[i].start_moment_ == group_pos)
infos_[i].rhythmic_importance_ = -2;
-
- // Mark the start of each unit up to the end of this beat group.
- for (int unit = 1; unit <= count; unit++)
+ // Work through the end of the beat group or the end of the beam
+ while (i < infos_.size () && infos_[i].start_moment_ < next_group_pos)
{
- Moment next_measure_pos = measure_pos + options.base_moment_;
-
- while (i < infos_.size () && infos_[i].start_moment_ < next_measure_pos)
+ Moment dt = infos_[i].start_moment_ - group_pos;
+ Rational tuplet = infos_[i].factor_;
+ Moment tuplet_moment (tuplet);
+ // set the beat end (if not in a tuplet) and increment the next beat
+ if (tuplet_count == 1 && infos_[i].start_moment_ == next_beat_pos)
{
- Moment dt = infos_[i].start_moment_ - measure_pos;
-
- // The rhythmic importance of a stem between beats depends on its fraction
- // of a beat: those stems with a lower denominator are deemed more
- // important.
- // FIXME: This is not the right way to do things for tuplets. For example,
- // in an 8th-note triplet with a quarter-note beat, 1/3 of a beat should be
- // more important than 1/2.
- if (infos_[i].rhythmic_importance_ >= 0)
- infos_[i].rhythmic_importance_ = (int) (dt / options.base_moment_).den ();
-
- i++;
+ infos_[i].rhythmic_importance_ = -1;
+ next_beat_pos += options.base_moment_;
}
+ // The rhythmic importance of a stem between beats depends on its fraction
+ // of a beat: those stems with a lower denominator are deemed more
+ // important. For tuplets, we need to make sure that we use
+ // the fraction of the tuplet, instead of the fraction of
+ // a beat.
+ Moment ratio = (dt / options.base_moment_ / tuplet_moment);
+ if (infos_[i].rhythmic_importance_ >= 0)
+ infos_[i].rhythmic_importance_ = (int) ratio.den ();
+ i++;
+ }
- measure_pos = next_measure_pos;
- if (i < infos_.size () && infos_[i].start_moment_ == measure_pos)
+ if (i < infos_.size () && infos_[i].start_moment_ == next_beat_pos)
+ {
+ if (tuplet_count == 1)
infos_[i].rhythmic_importance_ = -1;
+ next_beat_pos += options.base_moment_;
+ if (infos_[i].start_moment_ == next_group_pos)
+ infos_[i].rhythmic_importance_ = -2;
+ i++;
}
}
}
}
void
-Beaming_pattern::add_stem (Moment m, int b, bool invisible)
+Beaming_pattern::add_stem (Moment m, int b, bool invisible, Rational factor)
{
- infos_.push_back (Beam_rhythmic_element (m, b, invisible));
+ infos_.push_back (Beam_rhythmic_element (m, b, invisible, factor));
}
Beaming_pattern::Beaming_pattern ()
return infos_.at (i).invisible_;
}
+Rational
+Beaming_pattern::factor (int i) const
+{
+ return infos_.at (i).factor_;
+}
+
/*
- Split a beamin pattern at index i and return a new
+ Split a beaming pattern at index i and return a new
Beaming_pattern containing the removed elements
*/
Beaming_pattern *
count = max (beamlet_count (j, LEFT), beamlet_count (j, RIGHT));
new_pattern->add_stem (start_moment (j),
count,
- invisibility (j));
+ invisibility (j),
+ factor (j));
}
for (vsize j = i + 1; j < infos_.size ();)
infos_.pop_back ();
"dots "
"positioning-done "
"direction "
+ "note-collision "
);
#include "item.hh"
#include "pointer-group-interface.hh"
#include "spanner.hh"
+#include "system.hh"
#include "translator.icc"
vector<Drul_array<Spanner *> > annotated_spanners_;
void stop_translation_timestep ();
+ void finalize ();
void footnotify (Grob *, Stream_event *);
};
events_.clear ();
}
+void
+Footnote_engraver::finalize ()
+{
+ annotated_spanners_.resize (0);
+}
+
Footnote_engraver::Footnote_engraver ()
{
}
bool
Grob::vertical_less (Grob *g1, Grob *g2)
+{
+ return internal_vertical_less (g1, g2, false);
+}
+
+bool
+Grob::pure_vertical_less (Grob *g1, Grob *g2)
+{
+ return internal_vertical_less (g1, g2, true);
+}
+
+bool
+Grob::internal_vertical_less (Grob *g1, Grob *g2, bool pure)
{
Grob *vag = get_root_vertical_alignment (g1);
if (!vag)
extract_grob_set (vag, "elements", elts);
+ if (ag1 == ag2 && !pure)
+ {
+ Grob *common = g1->common_refpoint (g2, Y_AXIS);
+ return g1->relative_coordinate (common, Y_AXIS) > g2->relative_coordinate (common, Y_AXIS);
+ }
+
for (vsize i = 0; i < elts.size (); i++)
{
if (elts[i] == ag1)
"extra-X-extent "
"extra-Y-extent "
"extra-offset "
+ "forced-spacing "
"interfaces "
"layer "
"meta "
DECLARE_SCHEME_CALLBACK (calc_pure_relevant_items, (SCM));
DECLARE_SCHEME_CALLBACK (calc_pure_relevant_spanners, (SCM));
DECLARE_SCHEME_CALLBACK (calc_pure_y_common, (SCM));
+ static SCM internal_calc_pure_relevant_grobs (Grob *, string);
static Interval relative_group_extent (vector<Grob *> const &list,
Grob *common, Axis);
static Interval relative_maybe_bound_group_extent (vector<Grob *> const &list,
int rhythmic_importance_;
bool invisible_;
- Beam_rhythmic_element (Moment, int, bool);
+ Rational factor_;
+
+ Beam_rhythmic_element (Moment, int, bool, Rational);
Beam_rhythmic_element ();
int count (Direction d) const;
void beamify (Beaming_options const &);
void de_grace ();
- void add_stem (Moment d, int beams, bool invisible);
+ void add_stem (Moment d, int beams, bool invisible, Rational factor);
int beamlet_count (int idx, Direction d) const;
bool invisibility (int idx) const;
+ Rational factor (int idx) const;
Moment start_moment (int idx) const;
Moment end_moment (int idx) const;
Beaming_pattern *split_pattern (int idx);
static Grob *get_root_vertical_alignment (Grob *g);
static Grob *get_vertical_axis_group (Grob *g);
static bool vertical_less (Grob *g1, Grob *g2);
+ static bool pure_vertical_less (Grob *g1, Grob *g2);
+ static bool internal_vertical_less (Grob *g1, Grob *g2, bool pure);
static int get_vertical_axis_group_index (Grob *g);
virtual Interval_t<int> spanned_rank_interval () const;
DECLARE_SMOBS (Lily_parser);
friend int yyparse (void *);
- vector<Input> define_spots_;
-
char const *here_str0 () const;
Simultaneous_music *get_chord (Pitch tonic,
vector<Pitch> *adds, vector<Pitch> *subs,
class Pure_from_neighbor_interface
{
public:
- DECLARE_SCHEME_CALLBACK (filter_elements, (SCM));
+ DECLARE_SCHEME_CALLBACK (calc_pure_relevant_grobs, (SCM));
DECLARE_GROB_INTERFACE ();
};
static Interval get_spanned_interval (Grob *);
static void add_bar (Grob *, Grob *);
static void evaluate_glyph (Grob *);
+ static void notify_grobs_of_my_existence (Grob *);
DECLARE_SCHEME_CALLBACK (width, (SCM smob));
DECLARE_SCHEME_CALLBACK (print, (SCM));
DECLARE_SCHEME_CALLBACK (calc_glyph_name, (SCM));
void init_elements ();
friend class Paper_score; // ugh.
Paper_score *pscore_; // ugh.
- bool checked_footnotes_;
- vector<Grob *> footnote_grobs_; // TODO: make this a grob array
public:
Paper_score *paper_score () const;
vector<Real> get_footnote_heights_in_range (vsize st, vsize end);
vector<Real> get_in_note_heights_in_range (vsize st, vsize end);
vector<Real> internal_get_note_heights_in_range (vsize st, vsize end, bool foot);
- void get_footnote_grobs_in_range (vector<Grob *> &out, vsize st, vsize end);
- vector<Grob *> *footnote_grobs ();
+ vector<Grob *> get_footnote_grobs_in_range (vsize st, vsize end);
vsize num_footnotes ();
void do_break_substitution_and_fixup_refpoints ();
void post_processing ();
- void populate_footnote_grob_vector ();
SCM get_paper_system ();
SCM get_paper_systems ();
SCM get_broken_system_grobs ();
SCM get_broken_footnote_stencils ();
+ DECLARE_SCHEME_CALLBACK (footnotes_before_line_breaking, (SCM));
+ DECLARE_SCHEME_CALLBACK (footnotes_after_line_breaking, (SCM));
DECLARE_SCHEME_CALLBACK (calc_pure_relevant_grobs, (SCM));
DECLARE_SCHEME_CALLBACK (height, (SCM));
DECLARE_SCHEME_CALLBACK (calc_pure_height, (SCM, SCM, SCM));
scm_set_current_module (mod);
- if (!define_spots_.empty ())
- {
- define_spots_.back ().warning (_ ("braces do not match"));
- error_level_ = 1;
- }
-
error_level_ = error_level_ | lexer_->error_level_;
clear ();
}
do_yyparse ();
scm_set_current_module (mod);
- if (!define_spots_.empty ())
- {
- if (define_spots_.empty ()
- && !error_level_)
- programming_error ("define_spots_ don't match, but error_level_ not set.");
- }
-
error_level_ = error_level_ | lexer_->error_level_;
}
scm_set_current_module (mod);
- if (!define_spots_.empty ())
- {
- if (define_spots_.empty ()
- && !error_level_)
- programming_error ("define_spots_ don't match, but error_level_ not set.");
- }
-
error_level_ = error_level_ | lexer_->error_level_;
return result;
}
}
Stencil mol;
Stencil in_note_mol;
- for (vsize i = 0; i < sys->footnote_grobs ()->size (); i++)
+ extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
+ for (vsize i = 0; i < footnote_grobs.size (); i++)
{
- Grob *footnote = sys->footnote_grobs ()->at (i);
+ Grob *footnote = footnote_grobs[i];
SCM footnote_markup = footnote->get_property ("footnote-text");
if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
if (orig->is_broken ())
class Pure_from_neighbor_engraver : public Engraver
{
- vector<Grob *> items_then_;
- vector<Grob *> items_now_;
- vector<Grob *> pures_then_;
- vector<Grob *> pures_now_;
+ vector<Grob *> pure_relevants_;
+ vector<Grob *> need_pure_heights_from_neighbors_;
public:
TRANSLATOR_DECLARATIONS (Pure_from_neighbor_engraver);
protected:
DECLARE_ACKNOWLEDGER (pure_from_neighbor);
DECLARE_ACKNOWLEDGER (item);
- void stop_translation_timestep ();
+ void finalize ();
};
Pure_from_neighbor_engraver::Pure_from_neighbor_engraver ()
Pure_from_neighbor_engraver::acknowledge_item (Grob_info i)
{
SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
- if (!Pure_from_neighbor_interface::has_interface (i.grob ())
+ if (!Pure_from_neighbor_interface::has_interface (i.item ())
&& to_boolean (scm_call_1 (pure_relevant_p, i.item ()->self_scm ())))
- items_now_.push_back (i.item ());
+ pure_relevants_.push_back (i.item ());
}
-// note that this can get out of hand if there are lots of vertical axis groups...
-
void
Pure_from_neighbor_engraver::acknowledge_pure_from_neighbor (Grob_info i)
{
- pures_now_.push_back (i.item ());
+ need_pure_heights_from_neighbors_.push_back (i.item ());
}
void
-Pure_from_neighbor_engraver::stop_translation_timestep ()
+Pure_from_neighbor_engraver::finalize ()
{
- if (pures_now_.size ())
- {
- for (vsize i = 0; i < pures_now_.size (); i++)
- for (vsize j = 0; j < items_then_.size (); j++)
- Pointer_group_interface::add_grob (pures_now_[i], ly_symbol2scm ("elements"), items_then_[j]);
+ if (!need_pure_heights_from_neighbors_.size ())
+ return;
+
+ vector_sort (need_pure_heights_from_neighbors_, Grob::less);
+ vector_sort (pure_relevants_, Grob::less);
+
+ /*
+ first, clump need_pure_heights_from_neighbors into
+ vectors of grobs that have the same column.
+ */
- for (vsize i = 0; i < pures_then_.size (); i++)
- for (vsize j = 0; j < items_now_.size (); j++)
- Pointer_group_interface::add_grob (pures_then_[i], ly_symbol2scm ("elements"), items_now_[j]);
+ vsize l = 0;
+ vector<vector<Grob *> > need_pure_heights_from_neighbors;
+ do
+ {
+ vector<Grob *> temp;
+ temp.push_back (need_pure_heights_from_neighbors_[l]);
+ for (;
+ (l < need_pure_heights_from_neighbors_.size () - 1
+ && (need_pure_heights_from_neighbors_[l]->spanned_rank_interval ()[LEFT]
+ == need_pure_heights_from_neighbors_[l + 1]->spanned_rank_interval ()[LEFT]));
+ l++)
+ temp.push_back (need_pure_heights_from_neighbors_[l + 1]);
+ need_pure_heights_from_neighbors.push_back (temp);
+ l++;
+ }
+ while (l < need_pure_heights_from_neighbors_.size ());
- items_then_.clear ();
- items_then_.insert (items_then_.end (), items_now_.begin (), items_now_.end ());
- items_now_.clear ();
+ /*
+ then, loop through the pure_relevants_ list, adding the items
+ to the elements of need_pure_heights_from_neighbors_ on either side.
+ */
- pures_then_.clear ();
- pures_then_.insert (pures_then_.end (), pures_now_.begin (), pures_now_.end ());
- pures_now_.clear ();
+ int pos[2] = {-1, 0};
+ for (vsize i = 0; i < pure_relevants_.size (); i++)
+ {
+ if (pos[1] < (int) need_pure_heights_from_neighbors.size ()
+ && (pure_relevants_[i]->spanned_rank_interval ()[LEFT]
+ > need_pure_heights_from_neighbors[pos[1]][0]->spanned_rank_interval ()[LEFT]))
+ {
+ pos[0] = pos[1];
+ pos[1]++;
+ }
+ for (int j = 0; j < 2; j++)
+ if (pos[j] >= 0 && pos[j] < (int) need_pure_heights_from_neighbors.size ())
+ for (vsize k = 0; k < need_pure_heights_from_neighbors[pos[j]].size (); k++)
+ Pointer_group_interface::add_grob (need_pure_heights_from_neighbors[pos[j]][k], ly_symbol2scm ("neighbors"), pure_relevants_[i]);
}
+
+ need_pure_heights_from_neighbors_.clear ();
+ pure_relevants_.clear ();
}
#include "translator.icc"
along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "axis-group-interface.hh"
#include "grob.hh"
#include "grob-array.hh"
+#include "moment.hh"
+#include "paper-column.hh"
#include "pointer-group-interface.hh"
#include "pure-from-neighbor-interface.hh"
#include "spanner.hh"
#include "system.hh"
-MAKE_SCHEME_CALLBACK (Pure_from_neighbor_interface, filter_elements, 1);
+MAKE_SCHEME_CALLBACK (Pure_from_neighbor_interface, calc_pure_relevant_grobs, 1);
SCM
-Pure_from_neighbor_interface::filter_elements (SCM smob)
+Pure_from_neighbor_interface::calc_pure_relevant_grobs (SCM smob)
{
Grob *me = unsmob_grob (smob);
- extract_grob_set (me, "elements", elts);
+ extract_grob_set ((me->original () && me->original ()->is_live ()
+ ? me->original ()
+ : me),
+ "neighbors",
+ elts);
+
vector<Grob *> new_elts;
- Interval_t<int> srl = me->get_system ()->spanned_rank_interval ();
- for (vsize i = 0; i < elts.size (); i++)
- if (srl.contains (elts[i]->spanned_rank_interval ()[LEFT]))
- new_elts.push_back (elts[i]);
-
- SCM elements_scm = me->get_object ("elements");
- if (Grob_array::unsmob (elements_scm))
- {
- vector<Grob *> &arr
- = unsmob_grob_array (elements_scm)->array_reference ();
- arr = new_elts;
- }
-
- return SCM_BOOL_T;
+ new_elts.insert (new_elts.end (), elts.begin (), elts.end ());
+
+ SCM neighbors_scm = me->get_object ("neighbors");
+ if (Grob_array::unsmob (neighbors_scm))
+ {
+ vector<Grob *> &arr
+ = unsmob_grob_array (neighbors_scm)->array_reference ();
+ arr = new_elts;
+ }
+
+ return Axis_group_interface::internal_calc_pure_relevant_grobs (me, "neighbors");
}
ADD_INTERFACE (Pure_from_neighbor_interface,
"heights of the objects' neighbors.",
/* properties */
- "elements-filtered "
+ "neighbors "
"pure-relevant-grobs "
"pure-Y-common "
);
"positioning-done "
"head-direction "
"tie-configuration "
+ "ties "
);
/*
{
Grob *vag = Grob::get_root_vertical_alignment (bars_[0]);
if (vag)
- vector_sort (bars_, Grob::vertical_less);
+ vector_sort (bars_, Grob::pure_vertical_less);
spanbar_ = make_item ("SpanBar", SCM_EOL);
spanbar_->set_parent (bars_[0], X_AXIS);
SCM vis = bars_[0]->internal_get_property (vissym);
if (ly_is_equal (spanbar_->internal_get_property (vissym), vis))
spanbar_->set_property (vissym, vis);
-
+ Span_bar::notify_grobs_of_my_existence (spanbar_);
spanbar_ = 0;
}
bars_.resize (0);
#include "span-bar.hh"
#include "engraver.hh"
+/*
+ Note that span bar stubs exist for pure height calculations ONLY.
+ They should never be visually present on the page and should never
+ be engraved in contexts where BarLines are engraved.
+*/
+
class Span_bar_stub_engraver : public Engraver
{
vector<Grob *> spanbars_;
spanbars_.push_back (i.grob ());
}
-// note that this can get out of hand if there are lots of vertical axis groups...
-
void
Span_bar_stub_engraver::acknowledge_hara_kiri_group_spanner (Grob_info i)
{
gi.rerouting_daddy_context_ = affected_contexts[j];
announce_grob (gi);
if (!keep_extent[j])
- it->set_property ("Y-extent", ly_interval2scm (Interval (infinity_f, -infinity_f)));
+ it->suicide ();//it->set_property ("Y-extent", ly_interval2scm (Interval (infinity_f, -infinity_f)));
}
}
spanbars_.clear ();
return ly_string2scm (type);
}
+void
+Span_bar::notify_grobs_of_my_existence (Grob *me)
+{
+ extract_grob_set (me, "elements", elts);
+ vector<Grob *> sortable (elts.begin (), elts.end ());
+ vector_sort (sortable, Grob::vertical_less);
+ for (vsize i = 0; i < sortable.size (); i++)
+ sortable[i]->set_property ("has-span-bar",
+ scm_cons (scm_from_bool (i != 0),
+ scm_from_bool (i != sortable.size () - 1)));
+}
+
Interval
Span_bar::get_spanned_interval (Grob *me)
{
Real staff_space = Staff_symbol_referencer::staff_space (g);
return scm_from_double (staff_space);
}
+
+LY_DEFINE (ly_staff_symbol_staff_radius, "ly:staff-symbol-staff-radius",
+ 1, 0, 0, (SCM grob),
+ "Returns the radius of the staff associated with"
+ " @var{grob}.")
+{
+ LY_ASSERT_SMOB (Grob, grob, 1);
+ Grob *g = unsmob_grob (grob);
+ Real staff_radius = Staff_symbol_referencer::staff_radius (g);
+ return scm_from_double (staff_radius);
+}
"length "
"length-fraction "
"max-beam-connect "
+ "melody-spanner "
"neutral-direction "
"no-stem-extend "
"note-heads "
all_elements_ = 0;
pscore_ = 0;
rank_ = 0;
- checked_footnotes_ = false;
init_elements ();
}
{
all_elements_ = 0;
rank_ = 0;
- checked_footnotes_ = false;
init_elements ();
}
{
System *child = dynamic_cast<System *> (broken_intos_[i]);
child->all_elements_->remove_duplicates ();
+
for (vsize j = 0; j < child->all_elements_->size (); j++)
{
Grob *g = child->all_elements_->grob (j);
return lines;
}
-void
-System::populate_footnote_grob_vector ()
-{
- extract_grob_set (this, "all-elements", all_elts);
- for (vsize i = 0; i < all_elts.size (); i++)
- if (all_elts[i]->internal_has_interface (ly_symbol2scm ("footnote-interface")))
- footnote_grobs_.push_back (all_elts[i]);
-
- sort (footnote_grobs_.begin (), footnote_grobs_.end (), Grob::less);
- checked_footnotes_ = true;
-}
-
-void
-System::get_footnote_grobs_in_range (vector<Grob *> &out, vsize start, vsize end)
+vector<Grob *>
+System::get_footnote_grobs_in_range (vsize start, vsize end)
{
- if (!checked_footnotes_)
- populate_footnote_grob_vector ();
-
- for (vsize i = 0; i < footnote_grobs_.size (); i++)
+ vector<Grob *> out;
+ extract_grob_set (this, "footnotes-before-line-breaking", footnote_grobs);
+ for (vsize i = 0; i < footnote_grobs.size (); i++)
{
- int pos = footnote_grobs_[i]->spanned_rank_interval ()[LEFT];
- if (Spanner *s = dynamic_cast<Spanner *>(footnote_grobs_[i]))
+ Grob *at_bat = footnote_grobs[i];
+ int pos = at_bat->spanned_rank_interval ()[LEFT];
+ bool end_of_line_visible = true;
+ if (Spanner *s = dynamic_cast<Spanner *>(at_bat))
{
Direction spanner_placement = robust_scm2dir (s->get_property ("spanner-placement"), LEFT);
if (spanner_placement == CENTER)
spanner_placement = LEFT;
pos = s->spanned_rank_interval ()[spanner_placement];
+ if (s->original ())
+ {
+ Spanner *orig = dynamic_cast<Spanner *>(s->original ());
+ at_bat = spanner_placement == LEFT ? orig->broken_intos_[0] : orig->broken_intos_.back ();
+ pos = at_bat->spanned_rank_interval ()[RIGHT];
+ }
}
- if (Item *item = dynamic_cast<Item *>(footnote_grobs_[i]))
+ if (Item *item = dynamic_cast<Item *>(at_bat))
{
if (!Item::break_visible (item))
continue;
if (pos < int (start))
continue;
if (pos > int (end))
- break;
- if (!footnote_grobs_[i]->is_live ())
+ continue;
+ if (pos == int (start) && end_of_line_visible)
+ continue;
+ if (pos == int (end) && !end_of_line_visible)
+ continue;
+ if (!at_bat->is_live ())
continue;
- out.push_back (footnote_grobs_[i]);
+ out.push_back (at_bat);
}
+ return out;
}
vector<Real>
vector<Real>
System::internal_get_note_heights_in_range (vsize start, vsize end, bool foot)
{
- vector<Grob *> footnote_grobs;
- get_footnote_grobs_in_range (footnote_grobs, start, end);
+ vector<Grob *> footnote_grobs = get_footnote_grobs_in_range (start, end);
vector<Real> out;
for (vsize i = footnote_grobs.size (); i--;)
vsize
System::num_footnotes ()
{
- return footnote_grobs_.size ();
+ extract_grob_set (this, "footnotes-after-line-breaking", footnote_grobs);
+ return footnote_grobs.size ();
+}
+
+bool
+grob_2D_less (Grob *g1, Grob *g2)
+{
+ int sri[] = {0,0};
+ Grob *gs[] = {g1, g2};
+
+ for (int i = 0; i < 2; i++)
+ {
+ sri[i] = gs[i]->spanned_rank_interval ()[LEFT];
+ if (Spanner *s = dynamic_cast<Spanner *> (gs[i]))
+ {
+ if (s->broken_intos_.size ())
+ s = (scm_to_int (s->broken_intos_[0]->get_property ("spanner-placement")) == LEFT
+ ? s->broken_intos_[0]
+ : s->broken_intos_.back ());
+ gs[i] = s;
+ if (robust_scm2double (s->get_property ("X-offset"), 0.0) > 0)
+ sri[i] = s->spanned_rank_interval ()[RIGHT];
+ }
+ }
+
+ if (sri[0] == sri[1])
+ return Grob::vertical_less (gs[0], gs[1]);
+
+ return sri[0] < sri[1];
}
-vector<Grob *>*
-System::footnote_grobs ()
+MAKE_SCHEME_CALLBACK (System, footnotes_before_line_breaking, 1);
+SCM
+System::footnotes_before_line_breaking (SCM smob)
{
- return &footnote_grobs_;
+ Grob *me = unsmob_grob (smob);
+ vector<Grob *> footnotes;
+ SCM grobs_scm = Grob_array::make_array ();
+ extract_grob_set (me, "all-elements", elts);
+ for (vsize i = 0; i < elts.size (); i++)
+ if (elts[i]->internal_has_interface (ly_symbol2scm ("footnote-interface")))
+ footnotes.push_back (elts[i]);
+
+ unsmob_grob_array (grobs_scm)->set_array (footnotes);
+ return grobs_scm;
+}
+
+MAKE_SCHEME_CALLBACK (System, footnotes_after_line_breaking, 1);
+SCM
+System::footnotes_after_line_breaking (SCM smob)
+{
+ Spanner *sys_span = unsmob_spanner (smob);
+ System *sys = dynamic_cast<System *> (sys_span);
+ Interval_t<int> sri = sys->spanned_rank_interval ();
+ vector<Grob *> footnote_grobs = sys->get_footnote_grobs_in_range (sri[LEFT], sri[RIGHT]);
+ vector_sort (footnote_grobs, grob_2D_less);
+
+ SCM grobs_scm = Grob_array::make_array ();
+ unsmob_grob_array (grobs_scm)->set_array (footnote_grobs);
+ return grobs_scm;
}
void
Interval iv (pure_height (this, st, end));
system->set_property ("pure-Y-extent", ly_interval2scm (iv));
- get_footnote_grobs_in_range (system->footnote_grobs_, st, end);
-
system->set_bound (LEFT, c[0]);
system->set_bound (RIGHT, c.back ());
SCM system_labels = SCM_EOL;
"all-elements "
"columns "
"footnote-stencil "
+ "footnotes-before-line-breaking "
+ "footnotes-after-line-breaking "
"in-note-direction "
"in-note-padding "
"in-note-stencil "
/* properties */
"positioning-done "
"tie-configuration "
+ "ties "
);
\consists "Output_property_engraver"
\consists "Bar_engraver"
+ \consists "Pure_from_neighbor_engraver"
%% Bar_engraver must be first so default bars aren't overwritten
%% with empty ones.
the vertical edges: @code{(@var{left-height} . @var{right-height})}.")
(edge-text ,pair? "A pair specifying the texts to be set at the
edges: @code{(@var{left-text} . @var{right-text})}.")
- (elements-filtered ,boolean? "Callback to filter an element list.")
(round-up-exceptions ,list? "A list of pairs where car is the numerator
and cdr the denominator of a moment. Each pair in this list means that
the multi-measure rests of the corresponding length will be rounded up to
(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}.")
+ (forced-spacing ,number? "Spacing forced between grobs, used in
+various ligature engravers.")
(fraction ,fraction? "Numerator and denominator of a time
signature object.")
(french-beaming ,boolean? "Use French beaming style for this
shift dotted up-note to the right, rather than shifting just the
dot.")
-
;;
;; r
;;
(figures ,ly:grob-array? "Figured bass objects for continuation line.")
(flag ,ly:grob? "A pointer to a @code{Flag} object.")
(footnote-stencil ,ly:stencil? "The stencil of a system's footnotes.")
+ (footnotes-before-line-breaking ,ly:grob-array? "Footnote grobs of
+a whole system.")
+ (footnotes-after-line-breaking ,ly:grob-array? "Footnote grobs of
+a broken system.")
(full-score-pure-minimum-translations ,list? "A list of translations
for a full score's worth of grobs.")
column.")
(grace-spacing ,ly:grob? "A run of grace notes.")
+ (has-span-bar ,pair? "A pair of booleans indicating whether a a span bar
+is drawn above, or respectively below, this staff.")
(heads ,ly:grob-array? "An array of note heads.")
(items-worth-living ,ly:grob-array? "An array of interesting items. If
(left-neighbor ,ly:grob? "The right-most column that has a spacing-wish
for this column.")
+ (melody-spanner ,ly:grob? "The @code{MelodyItem} object for a stem.")
+
+ (neighbors ,ly:grob-array? "The X-axis neighbors of a grob. Used by the
+pure-from-neighbor-interface to determine various grob heights.")
+
(normal-stems ,ly:grob-array? "An array of visible stems.")
(note-columns ,ly:grob-array? "An array of @code{NoteColumn} grobs.")
(note-head ,ly:grob? "A single note head.")
(stems ,ly:grob-array? "An array of stem objects.")
(tie ,ly:grob? "A pointer to a @code{Tie} object.")
+ (ties ,ly:grob-array? "A grob array of @code{Tie} objects.")
(tremolo-flag ,ly:grob? "The tremolo object on a stem.")
(tuplet-number ,ly:grob? "The number for a bracket.")
(tuplets ,ly:grob-array? "An array of smaller tuplet brackets.")
(minimum-distances ,list? "A list of rods that have the format
@code{(@var{obj} . @var{dist})}.")
+ (note-collision ,ly:grob? "The @code{NoteCollision} object of a
+dot column.")
+
(positioning-done ,boolean? "Used to signal that a positioning element
did its job. This ensures that a positioning is only done once.")
(pure-Y-extent ,number-pair? "The estimated height of a system.")
(break-align-anchor . ,ly:bar-line::calc-anchor)
(break-align-symbol . staff-bar)
(break-visibility . ,bar-line::calc-break-visibility)
+ (extra-spacing-height . ,pure-from-neighbor-interface::account-for-span-bar)
(gap . 0.4)
(glyph . "|")
(glyph-name . ,bar-line::calc-glyph-name)
(right-edge . (extra-space . 0.0))))
(stencil . ,ly:bar-line::print)
(meta . ((class . Item)
+ (object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)
+ (pure-relevant-grobs . ,ly:pure-from-neighbor-interface::calc-pure-relevant-grobs)))
(interfaces . (bar-line-interface
break-aligned-interface
- font-interface))))))
+ font-interface
+ pure-from-neighbor-interface))))))
(BarNumber
. (
(break-align-anchor . ,ly:break-aligned-interface::calc-extent-aligned-anchor)
(break-align-symbol . clef)
(break-visibility . ,begin-of-line-visible)
+ ;(extra-spacing-height . ,pure-from-neighbor-interface::extra-spacing-height)
(glyph-name . ,ly:clef::calc-glyph-name)
(non-musical . #t)
(space-alist . ((cue-clef . (extra-space . 2.0))
(stencil . ,ly:clef::print)
(Y-offset . ,ly:staff-symbol-referencer::callback)
(meta . ((class . Item)
+ ;(object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)
+ ; (pure-relevant-grobs . ,ly:pure-from-neighbor-interface::calc-pure-relevant-grobs)))
(interfaces . (break-aligned-interface
clef-interface
font-interface
+ ;pure-from-neighbor-interface
staff-symbol-referencer-interface))))))
(ClusterSpanner
(break-align-anchor . ,ly:break-aligned-interface::calc-extent-aligned-anchor)
(break-align-symbol . cue-clef)
(break-visibility . ,begin-of-line-visible)
+ ;(extra-spacing-height . ,pure-from-neighbor-interface::extra-spacing-height)
(font-size . -4)
(glyph-name . ,ly:clef::calc-glyph-name)
(non-musical . #t)
(stencil . ,ly:clef::print)
(Y-offset . ,ly:staff-symbol-referencer::callback)
(meta . ((class . Item)
+ ;(object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)
+ ; (pure-relevant-grobs . ,ly:pure-from-neighbor-interface::calc-pure-relevant-grobs)))
(interfaces . (break-aligned-interface
clef-interface
font-interface
+ ;pure-from-neighbor-interface
staff-symbol-referencer-interface))))))
(CueEndClef
(break-align-anchor . ,ly:break-aligned-interface::calc-extent-aligned-anchor)
(break-align-symbol . cue-end-clef)
(break-visibility . ,begin-of-line-invisible)
+ ;(extra-spacing-height . ,pure-from-neighbor-interface::extra-spacing-height)
(font-size . -4)
(glyph-name . ,ly:clef::calc-glyph-name)
(non-musical . #t)
(stencil . ,ly:clef::print)
(Y-offset . ,ly:staff-symbol-referencer::callback)
(meta . ((class . Item)
+ ;(object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)
+ ; (pure-relevant-grobs . ,ly:pure-from-neighbor-interface::calc-pure-relevant-grobs)))
(interfaces . (break-aligned-interface
clef-interface
font-interface
+ ;pure-from-neighbor-interface
staff-symbol-referencer-interface))))))
(Custos
(first-note . (fixed-space . 2.5))))
(stencil . ,ly:key-signature-interface::print)
(extra-spacing-width . (0.0 . 1.0))
+ ;(extra-spacing-height . ,pure-from-neighbor-interface::extra-spacing-height-including-staff)
(Y-offset . ,ly:staff-symbol-referencer::callback)
(meta . ((class . Item)
+ ;(object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)
+ ; (pure-relevant-grobs . ,ly:pure-from-neighbor-interface::calc-pure-relevant-grobs)))
(interfaces . (break-aligned-interface
font-interface
key-signature-interface
+ ;pure-from-neighbor-interface
staff-symbol-referencer-interface))))))
(non-musical . #t)
(stencil . ,ly:span-bar::print)
(X-extent . ,ly:span-bar::width)
- (Y-extent . #f)
+ (Y-extent . (+inf.0 . -inf.0))
(meta . ((class . Item)
(interfaces . (bar-line-interface
font-interface
(SpanBarStub
. (
- (elements-filtered . ,ly:pure-from-neighbor-interface::filter-elements)
(X-extent . ,grob::x-parent-width)
- (Y-extent . ,span-bar-stub::height)
+ (Y-extent . ,(ly:make-unpure-pure-container #f ly:axis-group-interface::pure-height))
(meta . ((class . Item)
(object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)
- (pure-relevant-grobs . ,ly:axis-group-interface::calc-pure-relevant-grobs)))
+ (pure-relevant-grobs . ,ly:pure-from-neighbor-interface::calc-pure-relevant-grobs)))
(interfaces . (pure-from-neighbor-interface))))))
(StaffGrouper
(X-extent . ,ly:axis-group-interface::width)
(Y-extent . ,ly:system::height)
(meta . ((class . System)
- (object-callbacks . ((pure-relevant-grobs . ,ly:system::calc-pure-relevant-grobs)
+ (object-callbacks . ((footnotes-before-line-breaking . ,ly:system::footnotes-before-line-breaking)
+ (footnotes-after-line-breaking . ,ly:system::footnotes-after-line-breaking)
+ (pure-relevant-grobs . ,ly:system::calc-pure-relevant-grobs)
(pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)))
(interfaces . (axis-group-interface
system-interface))))))
(break-align-symbol . time-signature)
(break-align-anchor-alignment . ,LEFT)
(break-visibility . ,all-visible)
+ ;(extra-spacing-height . ,pure-from-neighbor-interface::extra-spacing-height-including-staff)
(extra-spacing-height . (-1.0 . 1.0))
(extra-spacing-width . (0.0 . 0.8))
(non-musical . #t)
(stencil . ,ly:time-signature::print)
(style . C)
(meta . ((class . Item)
+ ;(object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common)
+ ; (pure-relevant-grobs . ,ly:pure-from-neighbor-interface::calc-pure-relevant-grobs)))
(interfaces . (break-aligned-interface
font-interface
+ ;pure-from-neighbor-interface
time-signature-interface))))))
(TrillPitchAccidental
(,ly:side-position-interface::y-aligned-side . ,ly:side-position-interface::pure-y-aligned-side)
(,ly:slur::height . ,ly:slur::pure-height)
(,ly:slur::outside-slur-callback . ,ly:slur::pure-outside-slur-callback)
- (,span-bar-stub::height . ,ly:axis-group-interface::pure-height)
(,ly:stem::calc-stem-begin-position . ,ly:stem::pure-calc-stem-begin-position)
(,ly:stem::calc-stem-end-position . ,ly:stem::pure-calc-stem-end-position)
(,stem::length . ,stem::pure-length)
cautionary accidental.")
(change-to-id ,string? "Name of the context to change to.")
(change-to-type ,symbol? "Type of the context to change to.")
+ (class ,symbol? "The class name of an event class.")
(compress-procedure ,procedure? "Compress this music expression.
Arg@tie{}1: the music, arg@tie{}2: factor.")
+ (context ,ly:context? "The context to which an event is sent.")
(context-id ,string? "Name of context.")
(context-type ,symbol? "Type of context.")
(create-new ,boolean? "Create a fresh context.")
a sequential iterator. Takes a single music parameter.")
(error-found ,boolean?
"If true, a parsing error was found in this expression.")
+ (events ,list? "A list of events contained in this event.")
(figure ,integer? "A bass figure.")
(footnote-text ,markup? "Text to appear in a footnote.")
property, e.g., @code{(beamed-lengths details)}.")
(grob-value ,scheme? "The value of the grob property to set.")
+ (id ,symbol? "The ID of an event.")
(input-tag ,scheme? "Arbitrary marker to relate input and output.")
(inversion ,boolean? "If set, this chord note is inverted.")
(iterator-ctor ,procedure? "Function to construct a
whether to allow, forbid or force a line break.")
(metronome-count ,number-or-pair? "How many beats in a minute?")
+ (moment ,ly:moment? "The moment at which an event happens.")
+ (music-cause ,ly:music? "The music object that is the cause of
+an event.")
(name ,symbol? "Name of this music object.")
(no-continuation ,boolean? "If set, disallow continuation lines.")
(octavation ,integer? "This pitch was octavated by how many octaves?
For chord inversions, this is negative.")
(once ,boolean? "Apply this operation only during one time step?")
+ (ops ,scheme? "The operations to apply during the creation of a
+context.")
(origin ,ly:input-location? "Where was this piece of music defined?")
(original-chord ,ly:music? "Original chord of a repeated chord.
Used by repeated chords in \\relative mode, to determine the first note octave")
It must take a single argument, being the context.")
(property-operations ,list? "Do these operations for instantiating
the context.")
+ (property-path ,symbol? "The path of a property.")
(quoted-context-id ,string? "The ID of the context to direct quotes to,
e.g., @code{cue}.")
(equal? (ly:item-break-dir g) RIGHT))
(ly:grob-translate-axis! g 3.5 X)))
-(define-public (span-bar-stub::height grob)
- (ly:grob-property grob 'elements-filtered)
- (ly:axis-group-interface::height grob))
+(define-public (pure-from-neighbor-interface::extra-spacing-height grob)
+ (let* ((height (ly:grob::stencil-height grob))
+ (from-neighbors (interval-union
+ height
+ (ly:axis-group-interface::pure-height
+ grob
+ 0
+ 10000000))))
+ (coord-operation - from-neighbors height)))
+
+(define-public (pure-from-neighbor-interface::account-for-span-bar grob)
+ (define (other-op x) (x (cons cdr car)))
+ (let* ((esh (pure-from-neighbor-interface::extra-spacing-height grob))
+ (hsb (ly:grob-property grob 'has-span-bar)))
+ (if (pair? hsb)
+ (cons-map
+ (lambda (x)
+ (if (and ((other-op x) hsb)
+ (not (and (eq? x car)
+ (not (ly:grob-property grob 'allow-span-bar)))))
+ (x esh)
+ 0))
+ (cons car cdr))
+ '(0 . 0))))
+
+(define (pure-from-neighbor-interface::extra-spacing-height-including-staff grob)
+ (let ((esh (pure-from-neighbor-interface::extra-spacing-height grob))
+ (to-staff (coord-operation -
+ (interval-widen
+ '(0 . 0)
+ (ly:staff-symbol-staff-radius grob))
+ (ly:grob::stencil-height grob))))
+ (interval-union esh to-staff)))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Tuplets