--- /dev/null
+\version "2.15.22"
+
+\header {
+ texidoc = "LilyPond automatically shifts dynamics that collide with
+cross-staff stems when manual beams are used."
+}
+
+\new GrandStaff <<
+ \new Staff = "PnRH" {
+ \relative g {
+ \stemDown gis8 \p [ \change Staff = "PnLH" \stemUp a, \fff ]
+ \change Staff = "PnRH" r4
+ }
+ }
+ \new Staff = "PnLH" { \clef "F" { s4 r4 } }
+>>
#include "grob-array.hh"
#include "item.hh"
#include "international.hh"
+#include "interval-minefield.hh"
#include "least-squares.hh"
#include "libc-extension.hh"
#include "main.hh"
}
vector_sort (forbidden_intervals, Interval::left_less);
- Real epsilon = 1.0e-10;
Real beam_left_y = unquanted_y_[LEFT];
Interval feasible_beam_placements (beam_left_y, beam_left_y);
- /*
- forbidden_intervals contains a vector of intervals in which
- the beam cannot start. it iterates through these intervals,
- pushing feasible_beam_placements epsilon over or epsilon under a
- collision. when this type of change happens, the loop is marked
- as "dirty" and re-iterated.
-
- TODO: figure out a faster ways that this loop can happen via
- a better search algorithm and/or OOP.
- */
-
- bool dirty = false;
- do
- {
- dirty = false;
- for (vsize i = 0; i < forbidden_intervals.size (); i++)
- {
- Direction d = DOWN;
- do
- {
- if (forbidden_intervals[i][d] == d * infinity_f)
- feasible_beam_placements[d] = d * infinity_f;
- else if (forbidden_intervals[i].contains (feasible_beam_placements[d]))
- {
- feasible_beam_placements[d] = d * epsilon + forbidden_intervals[i][d];
- dirty = true;
- }
- }
- while (flip (&d) != DOWN);
- }
- }
- while (dirty);
+ Interval_minefield minefield (feasible_beam_placements, 0.0);
+ for (vsize i = 0; i < forbidden_intervals.size (); i++)
+ minefield.add_forbidden_interval (forbidden_intervals[i]);
+ minefield.solve ();
+ feasible_beam_placements = minefield.feasible_placements ();
// if the beam placement falls out of the feasible region, we push it
// to infinity so that it can never be a feasible candidate below
--- /dev/null
+/*
+ This file is part of LilyPond, the GNU music typesetter.
+
+ Copyright (C) 2011 Mike Solomon <mike@apollinemike.com>
+ Jan Nieuwenhuizen <janneke@gnu.org>
+
+ LilyPond is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ LilyPond is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INTERVAL_MINEFIELD_HH
+#define INTERVAL_MINEFIELD_HH
+
+#include "lily-proto.hh"
+#include "std-vector.hh"
+#include "interval.hh"
+
+class Interval_minefield
+{
+public :
+ Interval_minefield (Interval, Real);
+ void add_forbidden_interval (Interval forbidden);
+ Interval feasible_placements ();
+ void solve ();
+
+private :
+ vector<Interval> forbidden_intervals_;
+ Interval feasible_placements_;
+ Real bulk_;
+};
+
+#endif // INTERVAL_MINEFIELD_HH
\ No newline at end of file
static SCM aligned_on_self (Grob *me, Axis a, bool pure, int start, int end);
static SCM centered_on_object (Grob *me, Axis a);
static SCM aligned_on_parent (Grob *me, Axis a);
+ static SCM avoid_colliding_grobs (Grob *me, Axis a, Real offset);
static void set_center_parent (Grob *me, Axis a);
static void set_align_self (Grob *me, Axis a);
+ static void avoid_x_collisions (Grob *me);
DECLARE_SCHEME_CALLBACK (x_aligned_on_self, (SCM element));
DECLARE_SCHEME_CALLBACK (y_aligned_on_self, (SCM element));
DECLARE_SCHEME_CALLBACK (centered_on_x_parent, (SCM element));
DECLARE_SCHEME_CALLBACK (centered_on_y_parent, (SCM element));
DECLARE_SCHEME_CALLBACK (x_centered_on_y_parent, (SCM element));
-
+ DECLARE_SCHEME_CALLBACK (avoid_x_colliding_grobs, (SCM element, SCM offset));
+ DECLARE_SCHEME_CALLBACK (x_colliding_grobs, (SCM element));
DECLARE_SCHEME_CALLBACK (aligned_on_x_parent, (SCM element));
DECLARE_SCHEME_CALLBACK (aligned_on_y_parent, (SCM element));
};
--- /dev/null
+/*
+ This file is part of LilyPond, the GNU music typesetter.
+
+ Copyright (C) 2011 Mike Solomon <mike@apollinemike.com>
+ Jan Nieuwenhuizen <janneke@gnu.org>
+
+ LilyPond is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ LilyPond is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "interval-minefield.hh"
+#include "grob.hh"
+Interval_minefield::Interval_minefield (Interval feasible_placements, Real bulk)
+{
+ feasible_placements_ = feasible_placements;
+ bulk_ = bulk;
+}
+
+void
+Interval_minefield::add_forbidden_interval (Interval forbidden)
+{
+ forbidden_intervals_.push_back (forbidden);
+}
+
+Interval
+Interval_minefield::feasible_placements ()
+{
+ return feasible_placements_;
+}
+
+ /*
+ forbidden_intervals_ contains a vector of intervals in which
+ the beam cannot start. it iterates through these intervals,
+ pushing feasible_placements_ epsilon over or epsilon under a
+ collision. when this type of change happens, the loop is marked
+ as "dirty" and re-iterated.
+
+ TODO: figure out a faster ways that this loop can happen via
+ a better search algorithm.
+ */
+void
+Interval_minefield::solve()
+{
+ Real epsilon = 1.0e-10;
+ bool dirty = false;
+ do
+ {
+ dirty = false;
+ for (vsize i = 0; i < forbidden_intervals_.size (); i++)
+ {
+ Direction d = DOWN;
+ do
+ {
+ Interval feasible_widened = Interval (feasible_placements_[d], feasible_placements_[d]);
+ feasible_widened.widen (bulk_ / 2.);
+
+ if (forbidden_intervals_[i][d] == d * infinity_f)
+ feasible_placements_[d] = d * infinity_f;
+ else if (forbidden_intervals_[i].contains (feasible_widened[d])
+ || forbidden_intervals_[i].contains (feasible_widened[-d])
+ || feasible_widened.contains (forbidden_intervals_[i][d])
+ || feasible_widened.contains (forbidden_intervals_[i][-d]))
+ {
+ feasible_placements_[d] = forbidden_intervals_[i][d] + d * (epsilon + (bulk_ / 2));
+ dirty = true;
+ }
+ }
+ while (flip (&d) != DOWN);
+ }
+ }
+ while (dirty);
+}
\ No newline at end of file
if (script_ && !script_->get_parent (X_AXIS))
{
extract_grob_set (info.grob (), "note-heads", heads);
+ Grob *stem = unsmob_grob (info.grob ()->get_object ("stem"));
/*
Spacing constraints may require dynamics to be aligned on rests,
so check for a rest if this note column has no note heads.
{
script_->set_parent (x_parent, X_AXIS);
Self_alignment_interface::set_center_parent (script_, X_AXIS);
+ Self_alignment_interface::avoid_x_collisions (script_);
}
+ if (stem)
+ Pointer_group_interface::add_grob (script_, ly_symbol2scm ("potential-X-colliding-grobs"), stem);
}
if (current_spanner_ && !current_spanner_->get_bound (LEFT))
#include "self-alignment-interface.hh"
+#include "directional-element-interface.hh"
#include "grob.hh"
+#include "grob-array.hh"
+#include "interval-minefield.hh"
#include "paper-column.hh"
+#include "pointer-group-interface.hh"
#include "warn.hh"
MAKE_SCHEME_CALLBACK (Self_alignment_interface, y_aligned_on_self, 1);
return scm_from_double (x);
}
+MAKE_SCHEME_CALLBACK (Self_alignment_interface, avoid_x_colliding_grobs, 2);
+SCM
+Self_alignment_interface::avoid_x_colliding_grobs (SCM smob, SCM o)
+{
+ SCM avoided = avoid_colliding_grobs (unsmob_grob (smob), X_AXIS, robust_scm2double (o, 0.0));
+ return scm_is_null (avoided) ? o : avoided;
+}
+
+MAKE_SCHEME_CALLBACK (Self_alignment_interface, x_colliding_grobs, 1);
+SCM
+Self_alignment_interface::x_colliding_grobs (SCM smob)
+{
+ Grob *me = unsmob_grob (smob);
+ extract_grob_set (me, "potential-X-colliding-grobs", pot);
+ vector<Grob *> act;
+ Direction d = get_grob_direction (me->get_parent (Y_AXIS));
+ for (vsize i = 0; i < pot.size (); i++)
+ if (d == get_grob_direction (pot[i])
+ && to_boolean (pot[i]->get_property ("cross-staff")))
+ act.push_back (pot[i]);
+
+ SCM grobs_scm = Grob_array::make_array ();
+ unsmob_grob_array (grobs_scm)->set_array (act);
+
+ return grobs_scm;
+}
+
+SCM
+Self_alignment_interface::avoid_colliding_grobs (Grob *me, Axis a, Real offset)
+{
+ extract_grob_set (me, a == X_AXIS ? "X-colliding-grobs" : "Y-colliding-grobs", colls);
+ if (!colls.size ())
+ return SCM_EOL;
+ vector<Interval> ivs;
+
+ Item *refp = dynamic_cast<Item *> (common_refpoint_of_array (colls, me, a));
+ if (!refp)
+ return SCM_EOL;
+
+ Interval iv = me->extent (me, a) + offset;
+ for (vsize i = 0; i < colls.size (); i++)
+ ivs.push_back (colls[i]->extent (refp, a));
+
+ Interval_minefield minefield (Interval (iv.center (), iv.center ()), iv.length ());
+ for (vsize i = 0; i < ivs.size (); i++)
+ minefield.add_forbidden_interval (ivs[i]);
+ minefield.solve ();
+ Interval pos = minefield.feasible_placements ();
+
+ if (pos[LEFT] == pos[RIGHT])
+ return SCM_EOL;
+
+ Direction col_dir = ((abs (pos[LEFT] - iv.center ())
+ + robust_scm2double (me->get_property ("collision-bias"), 0.0))
+ > abs (pos[RIGHT] - iv.center ()))
+ ? RIGHT
+ : LEFT;
+
+ return scm_from_double ((pos[col_dir] - (iv.length () / 2)
+ + col_dir
+ * robust_scm2double (me->get_property ("collision-padding"), 0.0)));
+}
+
void
Self_alignment_interface::set_center_parent (Grob *me, Axis a)
{
a);
}
+void
+Self_alignment_interface::avoid_x_collisions (Grob *me)
+{
+ chain_offset_callback (me, avoid_x_colliding_grobs_proc, X_AXIS);
+}
+
void
Self_alignment_interface::set_align_self (Grob *me, Axis a)
{
"@end table\n",
/* properties */
+ "collision-bias "
+ "collision-padding "
+ "potential-X-colliding-grobs "
"self-alignment-X "
"self-alignment-Y "
+ "X-colliding-grobs "
+ "Y-colliding-grobs "
);
edges of beams?")
(collapse-height ,ly:dimension? "Minimum height of system start
delimiter. If equal or smaller, the bracket/@/brace/@/line is removed.")
+ (collision-bias ,number? "Number determining how much to favor the
+left (negative) or right (positive). Larger absolute values in either
+direction will push a collision in this direction.")
(collision-interfaces ,list? "A list of interfaces for which
automatic beam-collision resolution is run.")
+ (collision-padding ,number? "Amount of padding to apply after
+a collision is detected via the self-alignment-interface.")
(collision-voice-only ,boolean? "Does automatic beam collsion apply
only to the voice in which the beam was created?")
(color ,color? "The color of this grob.")
(note-heads ,ly:grob-array? "An array of note head grobs.")
(pedal-text ,ly:grob? "A pointer to the text of a mixed-style piano
pedal.")
+ (potential-X-colliding-grobs ,ly:grob-array? "Grobs that can potentially
+collide with a self-aligned grob on the X-axis.")
(pure-relevant-grobs ,ly:grob-array? "All the grobs (items and spanners)
that are relevant for finding the @code{pure-Y-extent}")
(pure-relevant-items ,ly:grob-array? "A subset of elements that are
(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.")
+ (X-colliding-grobs ,ly:grob-array? "Grobs that can collide
+with a self-aligned grob on the X-axis.")
+ (Y-colliding-grobs ,ly:grob-array? "Grobs that can collide
+with a self-aligned grob on the Y-axis.")
(X-common ,ly:grob? "Common reference point for axis group.")
(Y-common ,ly:grob? "See @code{X-common}.")
;; todo.
+ (collision-bias . -2.0)
+ (collision-padding . 0.5)
(direction . ,ly:script-interface::calc-direction)
(extra-spacing-width . (+inf.0 . -inf.0))
(font-encoding . fetaText)
(X-offset . ,ly:self-alignment-interface::x-aligned-on-self)
(Y-offset . ,ly:self-alignment-interface::y-aligned-on-self)
(meta . ((class . Item)
+ (object-callbacks . ((X-colliding-grobs . ,ly:self-alignment-interface::x-colliding-grobs)))
(interfaces . (dynamic-interface
dynamic-text-interface
font-interface
(ly:text-interface::interpret-markup layout props text)))
-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; beam slope