Includes a regression test.
--- /dev/null
+\version "2.19.34"
+
+\header {
+ texidoc = "Whether simultaneous notes are identified as
+ vertically colliding or not depends on the value of the
+ @code{note-collision-threshold} property of the @code{Stem}
+ grob (for notes in the same voice) and the @code{NoteCollision}
+ grob (for notes in different voices)."
+}
+
+music = <<
+ \new Voice \relative {
+ \voiceOne
+ <c' d>4 <d e> <f g> <g a>
+ e g g a
+
+ }
+ \new Voice \relative {
+ \voiceTwo
+ s4 s s s
+ d' f a b
+ }
+>>
+
+customizations = \with {
+ staffLineLayoutFunction = #ly:pitch-semitones
+ \override StaffSymbol.staff-space = #0.7
+ \override StaffSymbol.line-positions = #'(-4 0 4)
+}
+
+
+\markup "collisions"
+
+\new Staff \with {
+ \customizations
+}
+\music
+
+\markup "collisions prevented"
+
+\new Staff \with {
+ \customizations
+ \override Stem.note-collision-threshold = #2
+ \override NoteCollision.note-collision-threshold = #2
+}
+\music
vector<int> ups = Stem::note_head_positions (stems[UP]);
vector<int> dps = Stem::note_head_positions (stems[DOWN]);
+ int threshold = robust_scm2int (me->get_property ("note-collision-threshold"), 1);
+
/* Too far apart to collide. */
- if (ups[0] > dps.back () + 1)
+ if (ups[0] > dps.back () + threshold)
return 0.0;
/* If the chords just 'touch' their extreme noteheads,
*/
bool touch = false;
if (ups[0] >= dps.back ()
- && (dps.size () < 2 || ups[0] >= dps[dps.size () - 2] + 2)
- && (ups.size () < 2 || ups[1] >= dps.back () + 2))
+ && (dps.size () < 2 || ups[0] >= dps[dps.size () - 2] + threshold + 1)
+ && (ups.size () < 2 || ups[1] >= dps.back () + threshold + 1))
touch = true;
/* Filter out the 'o's in this configuration, since they're no
for (vsize i = 0, j = 0; i < ups.size () && j < dps.size ();)
{
- if (abs (ups[i] - dps[j]) == 1)
+ if (ups[i] == dps[j])
+ full_collide = true;
+ else if (abs (ups[i] - dps[j]) <= threshold)
{
merge_possible = false;
if (ups[i] > dps[j])
else
distant_half_collide = true;
}
- else if (ups[i] == dps[j])
- full_collide = true;
else if (ups[i] > dps[0] && ups[i] < dps.back ())
merge_possible = false;
else if (dps[j] > ups[0] && dps[j] < ups.back ())
/* properties */
"merge-differently-dotted "
"merge-differently-headed "
+ "note-collision-threshold "
"positioning-done "
"prefer-dotted-right "
);
}
bool parity = true;
Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
+ int threshold = robust_scm2int (me->get_property ("note-collision-threshold"), 1);
for (vsize i = 1; i < heads.size (); i++)
{
Real p = Staff_symbol_referencer::get_position (heads[i]);
dy should always be 0.5, 0.0, 1.0, but provide safety margin
for rounding errors.
*/
- if (dy < 1.1)
+ if (dy < 0.1 + threshold)
{
if (parity)
{
"neutral-direction "
"no-stem-extend "
"note-heads "
+ "note-collision-threshold "
"positioning-done "
"rests "
"stem-begin-position "
between 0 and 1.")
(note-names ,vector? "Vector of strings containing names for
easy-notation note heads.")
+ (note-collision-threshold ,ly:dimension? "Simultaneous notes that
+are this close or closer in units of @code{staff-space} will be
+identified as vertically colliding. Used by @code{Stem} grobs for notes
+in the same voice, and @code{NoteCollision} grobs for notes in
+different voices. Default value@tie{}1.")
(number-type ,symbol? "Numbering style. Choices include
@code{roman-lower}, @code{roman-upper} and @code{arabic}.")
(NoteCollision
. (
(axes . (,X ,Y))
+ (note-collision-threshold . 1)
(positioning-done . ,ly:note-collision-interface::calc-positioning-done)
(prefer-dotted-right . #t)
(X-extent . ,ly:axis-group-interface::width)
(duration-log . ,stem::calc-duration-log)
(length . ,(ly:make-unpure-pure-container ly:stem::calc-length ly:stem::pure-calc-length))
(neutral-direction . ,DOWN)
+ (note-collision-threshold . 1)
(positioning-done . ,ly:stem::calc-positioning-done)
(stem-info . ,ly:stem::calc-stem-info)
(stem-begin-position . ,(ly:make-unpure-pure-container ly:stem::calc-stem-begin-position ly:stem::pure-calc-stem-begin-position))