]> git.donarmstrong.com Git - lilypond.git/commitdiff
Prevents dynamics from colliding with cross-staff stems.
authorMike Solomon <mike@apollinemike.com>
Thu, 15 Dec 2011 15:49:40 +0000 (16:49 +0100)
committerMike Solomon <mike@apollinemike.com>
Thu, 15 Dec 2011 15:49:40 +0000 (16:49 +0100)
Creates an Interval_minefield class that tracks intersections of
intevals and returns the closest "safe spots" to the original interval
on either side.  Dynamics are then moved to one of these two safe spots
depending on the collision-bias property (with negative values forcing
the decision towards the left and positive towards the right).

input/regression/dynamics-avoid-cross-staff-stem.ly [new file with mode: 0644]
lily/beam-quanting.cc
lily/include/interval-minefield.hh [new file with mode: 0644]
lily/include/self-alignment-interface.hh
lily/interval-minefield.cc [new file with mode: 0644]
lily/new-dynamic-engraver.cc
lily/self-alignment-interface.cc
scm/define-grob-properties.scm
scm/define-grobs.scm
scm/output-lib.scm

diff --git a/input/regression/dynamics-avoid-cross-staff-stem.ly b/input/regression/dynamics-avoid-cross-staff-stem.ly
new file mode 100644 (file)
index 0000000..fdf1a77
--- /dev/null
@@ -0,0 +1,16 @@
+\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 } }
+>>
index 57cf25853dbbc49fd6ce3073b0f0f18172e76604..e971d6ee9d14f8a734105f80c896430363c436da 100644 (file)
@@ -33,6 +33,7 @@ using namespace std;
 #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"
@@ -811,42 +812,14 @@ Beam_scoring_problem::shift_region_to_valid ()
     }
 
   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
diff --git a/lily/include/interval-minefield.hh b/lily/include/interval-minefield.hh
new file mode 100644 (file)
index 0000000..62136b0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+  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
index 8ecde08a3e2f03b581e9611b44d8ae776935d570..80a7c0d37ac9d8511b6557fe89f8cf889caf8881 100644 (file)
@@ -30,8 +30,10 @@ struct Self_alignment_interface
   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));
@@ -41,7 +43,8 @@ struct Self_alignment_interface
   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));
 };
diff --git a/lily/interval-minefield.cc b/lily/interval-minefield.cc
new file mode 100644 (file)
index 0000000..5fe3414
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+  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
index 3554b22e9ee00a290fabdf0c47eb8245e3e5f74a..1418e563f5aad1e10a4865770b879315f7a40e0b 100644 (file)
@@ -260,6 +260,7 @@ New_dynamic_engraver::acknowledge_note_column (Grob_info info)
   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.
@@ -271,7 +272,10 @@ New_dynamic_engraver::acknowledge_note_column (Grob_info info)
         {
           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))
index 0c7215233338ebb32c277e2af1a2c88ec154c216..a6fde153e043e826b4865c467c3267d6bdeb1685 100644 (file)
 
 #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);
@@ -134,6 +138,69 @@ Self_alignment_interface::aligned_on_parent (Grob *me, Axis a)
   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)
 {
@@ -142,6 +209,12 @@ 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)
 {
@@ -165,6 +238,11 @@ ADD_INTERFACE (Self_alignment_interface,
                "@end table\n",
 
                /* properties */
+               "collision-bias "
+               "collision-padding "
+               "potential-X-colliding-grobs "
                "self-alignment-X "
                "self-alignment-Y "
+               "X-colliding-grobs "
+               "Y-colliding-grobs "
               );
index c26bd3cae19a503c10ee9e3eb7c586e8498adc9a..f209fe649ac63f42306339cb40ea9658934d770d 100644 (file)
@@ -174,8 +174,13 @@ hairpins (al/@/del niente).")
 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.")
@@ -1070,6 +1075,8 @@ pure-from-neighbor-interface to determine various grob heights.")
      (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
@@ -1110,6 +1117,10 @@ results, use @code{LEFT} and @code{RIGHT}.")
      (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}.")
 
index 653bc5e227046d944a281a5d34558f68005e4007..806f720697814d4094e08348b7fc2d6fd9f02230 100644 (file)
 
        ;; 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
index 3e2d5ff5201e9a103e49b08b8aa61983c91270fe..9137d5be170845fd9e0d9cd498dc425c01575982 100644 (file)
@@ -58,7 +58,6 @@
 
     (ly:text-interface::interpret-markup layout props text)))
 
-
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; beam slope