2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2010 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "note-collision.hh"
22 #include "axis-group-interface.hh"
23 #include "dot-column.hh"
24 #include "international.hh"
25 #include "note-column.hh"
26 #include "note-head.hh"
27 #include "output-def.hh"
28 #include "pointer-group-interface.hh"
30 #include "rhythmic-head.hh"
31 #include "staff-symbol-referencer.hh"
32 #include "side-position-interface.hh"
38 check_meshing_chords (Grob
*me
,
39 Drul_array
<vector
<Real
> > *offsets
,
40 Drul_array
<vector
<Slice
> > const &extents
,
41 Drul_array
<vector
<Grob
*> > const &clash_groups
)
44 if (!extents
[UP
].size () || !extents
[DOWN
].size ())
47 Grob
*clash_up
= clash_groups
[UP
][0];
48 Grob
*clash_down
= clash_groups
[DOWN
][0];
50 /* Every note column should have a stem, but avoid a crash. */
51 if (!Note_column::get_stem (clash_up
) || !Note_column::get_stem (clash_down
))
54 Drul_array
<Grob
*> stems (Note_column::get_stem (clash_down
),
55 Note_column::get_stem (clash_up
));
57 Grob
*head_up
= Note_column::first_head (clash_up
);
58 Grob
*head_down
= Note_column::first_head (clash_down
);
60 vector
<int> ups
= Stem::note_head_positions (Note_column::get_stem (clash_up
));
61 vector
<int> dps
= Stem::note_head_positions (Note_column::get_stem (clash_down
));
63 /* Too far apart to collide. */
64 if (ups
[0] > dps
.back () + 1)
67 /* Merge heads if the notes lie the same line, or if the "stem-up-note" is
68 above the "stem-down-note". */
69 bool merge_possible
= (ups
[0] >= dps
[0]) && (ups
.back () >= dps
.back ());
71 /* Do not merge notes typeset in different style. */
72 if (!ly_is_equal (head_up
->get_property ("style"),
73 head_down
->get_property ("style")))
74 merge_possible
= false;
76 int up_ball_type
= Rhythmic_head::duration_log (head_up
);
77 int down_ball_type
= Rhythmic_head::duration_log (head_down
);
79 /* Do not merge whole notes (or longer, like breve, longa, maxima). */
80 if (merge_possible
&& (up_ball_type
<= 0 || down_ball_type
<= 0))
81 merge_possible
= false;
84 && Rhythmic_head::dot_count (head_up
) != Rhythmic_head::dot_count (head_down
)
85 && !to_boolean (me
->get_property ("merge-differently-dotted")))
86 merge_possible
= false;
88 /* Can only merge different heads if merge-differently-headed is set. */
90 && up_ball_type
!= down_ball_type
91 && !to_boolean (me
->get_property ("merge-differently-headed")))
92 merge_possible
= false;
94 /* Should never merge quarter and half notes, as this would make
95 them indistinguishable. */
97 && ((Stem::duration_log (stems
[UP
]) == 1
98 && Stem::duration_log (stems
[DOWN
]) == 2)
99 || (Stem::duration_log (stems
[UP
]) == 2
100 && Stem::duration_log (stems
[DOWN
]) == 1)))
101 merge_possible
= false;
104 this case (distant half collide),
111 the noteheads may be closer than this case (close half collide)
122 /* TODO: filter out the 'o's in this configuration, since they're no
123 part in the collision.
132 bool close_half_collide
= false;
133 bool distant_half_collide
= false;
134 bool full_collide
= false;
136 for (vsize i
= 0, j
= 0; i
< ups
.size () && j
< dps
.size (); )
138 if (abs (ups
[i
] - dps
[j
]) == 1)
140 merge_possible
= false;
142 close_half_collide
= true;
144 distant_half_collide
= true;
146 else if (ups
[i
] == dps
[j
])
148 else if (ups
[i
] > dps
[0] && ups
[i
] < dps
.back ())
149 merge_possible
= false;
150 else if (dps
[j
] > ups
[0] && dps
[j
] < ups
.back ())
151 merge_possible
= false;
155 else if (ups
[i
] > dps
[j
])
164 full_collide
= full_collide
|| (close_half_collide
165 && distant_half_collide
);
167 Real shift_amount
= 1;
169 bool touch
= (ups
[0] >= dps
.back ());
170 /* As a special case, if the topmost part of the downstem chord is a second,
171 the top note of which is the same pitch as the lowest upstem note, they
172 shouldn't count as touching.
174 if (dps
.back () == ups
[0] && dps
.size () > 1 && dps
[dps
.size() - 2] == ups
[0] - 1)
180 /* For full collisions, the right hand head may obscure dots, so
181 make sure the dotted heads go to the right. */
182 bool stem_to_stem
= false;
185 if (Rhythmic_head::dot_count (head_up
) > Rhythmic_head::dot_count (head_down
))
187 else if (Rhythmic_head::dot_count (head_up
) < Rhythmic_head::dot_count (head_down
))
191 /* The solfa is a triangle, which is inverted depending on stem
192 direction. In case of a collision, one of them should be removed,
193 so the resulting note does not look like a block.
195 SCM up_style
= head_up
->get_property ("style");
196 SCM down_style
= head_down
->get_property ("style");
198 && (up_style
== ly_symbol2scm ("fa") || up_style
== ly_symbol2scm ("faThin"))
199 && (down_style
== ly_symbol2scm ("fa") || down_style
== ly_symbol2scm ("faThin")))
201 Interval uphead_size
= head_up
->extent (head_up
, Y_AXIS
);
202 Offset att
= Offset (0.0, -1.0);
203 head_up
->set_property ("stem-attachment", ly_offset2scm (att
));
204 head_up
->set_property ("transparent", SCM_BOOL_T
);
211 /* If possible, don't wipe any heads. Else, wipe shortest head,
212 or head with smallest amount of dots. Note: when merging
213 different heads, dots on the smaller one disappear. */
215 Grob
*dot_wipe_head
= head_up
;
217 if (up_ball_type
== down_ball_type
)
219 if (Rhythmic_head::dot_count (head_down
) < Rhythmic_head::dot_count (head_up
))
221 wipe_ball
= head_down
;
222 dot_wipe_head
= head_down
;
224 else if (Rhythmic_head::dot_count (head_down
) > Rhythmic_head::dot_count (head_up
))
226 dot_wipe_head
= head_up
;
230 dot_wipe_head
= head_up
;
232 else if (down_ball_type
> up_ball_type
)
234 wipe_ball
= head_down
;
235 dot_wipe_head
= head_down
;
237 else if (down_ball_type
< up_ball_type
)
240 dot_wipe_head
= head_up
;
242 If upper head is eighth note or shorter, and lower head is half note,
243 shift by the difference between the open and filled note head widths,
244 otherwise upper stem will be misaligned slightly.
246 if (Stem::duration_log (stems
[DOWN
]) == 1
247 && Stem::duration_log (stems
[UP
]) >= 3)
248 shift_amount
= (1 - head_up
->extent (head_up
, X_AXIS
).length () /
249 head_down
->extent (head_down
, X_AXIS
).length ()) * 0.5;
254 if (Grob
*d
= unsmob_grob (dot_wipe_head
->get_object ("dot")))
258 if (wipe_ball
&& wipe_ball
->is_live ())
259 wipe_ball
->set_property ("transparent", SCM_BOOL_T
);
261 /* TODO: these numbers are magic; should devise a set of grob props
262 to tune this behavior. */
263 else if (stem_to_stem
)
264 shift_amount
= -abs (shift_amount
) * 0.65;
265 else if (close_half_collide
&& !touch
)
266 shift_amount
*= 0.52;
267 else if (distant_half_collide
&& !touch
)
269 else if (distant_half_collide
|| close_half_collide
|| full_collide
)
273 else if (Rhythmic_head::dot_count (head_up
) || Rhythmic_head::dot_count (head_down
))
276 shift_amount
*= 0.17;
282 && down_ball_type
* up_ball_type
== 0)
284 if (up_ball_type
== 0 && down_ball_type
== 1)
285 shift_amount
*= 1.25;
286 else if (up_ball_type
== 0 && down_ball_type
== 2)
287 shift_amount
*= 1.35;
288 else if (down_ball_type
== 0 && up_ball_type
== 1)
290 else if (down_ball_type
== 0 && up_ball_type
== 2)
291 shift_amount
*= 0.75;
297 * Dots from left note head collide with right note head. Only occurs
298 * with a close half collide, if the left note head is between
299 * lines and the right note head is on a line, and if right note head
300 * hasn't got any dots.
302 if (close_half_collide
303 && Rhythmic_head::dot_count (head_up
)
304 && !Rhythmic_head::dot_count (head_down
))
306 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
307 if (!Staff_symbol_referencer::on_line (staff
, ups
[0]))
310 TODO: consider junking the else body.
312 if (to_boolean (me
->get_property ("prefer-dotted-right")))
316 Grob
*d
= unsmob_grob (head_up
->get_object ("dot"));
317 Grob
*parent
= d
->get_parent (X_AXIS
);
318 if (Dot_column::has_interface (parent
))
319 Side_position_interface::add_support (parent
, head_down
);
324 /* For full or close half collisions, the right hand head may
325 obscure dots. Move dots to the right. */
326 if (abs (shift_amount
) > 1e-6
327 && Rhythmic_head::dot_count (head_down
) > Rhythmic_head::dot_count (head_up
)
328 && (full_collide
|| close_half_collide
))
330 Grob
*d
= unsmob_grob (head_down
->get_object ("dot"));
331 Grob
*parent
= d
->get_parent (X_AXIS
);
341 the . is put right of o which is erroneous o force-shifted
344 if (Dot_column::has_interface (parent
))
346 Grob
*stem
= unsmob_grob (head_up
->get_object ("stem"));
347 extract_grob_set (stem
, "note-heads", heads
);
348 for (vsize i
= 0; i
< heads
.size (); i
++)
349 Side_position_interface::add_support (parent
, heads
[i
]);
356 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
357 (*offsets
)[d
][i
] += d
* shift_amount
;
359 while ((flip (&d
)) != UP
);
363 MAKE_SCHEME_CALLBACK (Note_collision_interface
, calc_positioning_done
, 1)
365 Note_collision_interface::calc_positioning_done (SCM smob
)
367 Grob
*me
= unsmob_grob (smob
);
368 me
->set_property ("positioning-done", SCM_BOOL_T
);
370 Drul_array
<vector
<Grob
*> > clash_groups
= get_clash_groups (me
);
375 for (vsize i
= clash_groups
[d
].size (); i
--; )
380 clash_groups
[d
][i
]->extent (me
, X_AXIS
);
383 while (flip (&d
) != UP
);
385 SCM
autos (automatic_shift (me
, clash_groups
));
386 SCM
hand (forced_shift (me
));
391 if (clash_groups
[d
].size ())
393 Grob
*h
= clash_groups
[d
][0];
394 Grob
*fh
= Note_column::first_head (h
);
396 wid
= fh
->extent (h
, X_AXIS
).length ();
399 while (flip (&d
) != UP
);
402 Real left_most
= 1e6
;
404 vector
<Real
> amounts
;
405 for (; scm_is_pair (hand
); hand
= scm_cdr (hand
))
407 Grob
*s
= unsmob_grob (scm_caar (hand
));
408 Real amount
= scm_to_double (scm_cdar (hand
)) * wid
;
411 amounts
.push_back (amount
);
412 if (amount
< left_most
)
415 for (; scm_is_pair (autos
); autos
= scm_cdr (autos
))
417 Grob
*s
= unsmob_grob (scm_caar (autos
));
418 Real amount
= scm_to_double (scm_cdar (autos
)) * wid
;
420 vsize x
= find (done
, s
) - done
.begin ();
421 if (x
== VPOS
|| x
>= done
.size ())
424 amounts
.push_back (amount
);
425 if (amount
< left_most
)
430 for (vsize i
= 0; i
< amounts
.size (); i
++)
431 done
[i
]->translate_axis (amounts
[i
] - left_most
, X_AXIS
);
436 Drul_array
< vector
<Grob
*> >
437 Note_collision_interface::get_clash_groups (Grob
*me
)
439 Drul_array
<vector
<Grob
*> > clash_groups
;
441 extract_grob_set (me
, "elements", elements
);
442 for (vsize i
= 0; i
< elements
.size (); i
++)
444 Grob
*se
= elements
[i
];
445 if (Note_column::has_interface (se
))
447 if (!Note_column::dir (se
))
448 se
->programming_error ("note-column has no direction");
450 clash_groups
[Note_column::dir (se
)].push_back (se
);
457 vector
<Grob
*> &clashes (clash_groups
[d
]);
458 vector_sort (clashes
, Note_column::shift_less
);
460 while ((flip (&d
)) != UP
);
466 This complicated routine moves note columns around horizontally to
467 ensure that notes don't clash.
470 Note_collision_interface::automatic_shift (Grob
*me
,
471 Drul_array
<vector
<Grob
*> > clash_groups
)
473 Drul_array
< vector
<int> > shifts
;
479 vector
<int> &shift (shifts
[d
]);
480 vector
<Grob
*> &clashes (clash_groups
[d
]);
482 for (vsize i
= 0; i
< clashes
.size (); i
++)
485 = clashes
[i
]->get_property ("horizontal-shift");
487 if (scm_is_number (sh
))
488 shift
.push_back (scm_to_int (sh
));
493 for (vsize i
= 1; i
< shift
.size (); i
++)
495 if (shift
[i
- 1] == shift
[i
])
497 clashes
[0]->warning (_ ("ignoring too many clashing note columns"));
502 while ((flip (&d
)) != UP
);
504 Drul_array
<vector
<Slice
> > extents
;
505 Drul_array
<vector
<Real
> > offsets
;
509 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
511 Slice
s (Note_column::head_positions_interval (clash_groups
[d
][i
]));
514 extents
[d
].push_back (s
);
515 offsets
[d
].push_back (d
* 0.5 * i
);
518 while ((flip (&d
)) != UP
);
521 do horizontal shifts of each direction
531 for (vsize i
= 1; i
< clash_groups
[d
].size (); i
++)
533 Slice prev
= extents
[d
][i
- 1];
534 prev
.intersect (extents
[d
][i
]);
535 if (prev
.length () > 0
536 || (extents
[-d
].size () && d
* (extents
[d
][i
][-d
] - extents
[-d
][0][d
]) < 0))
537 for (vsize j
= i
; j
< clash_groups
[d
].size (); j
++)
538 offsets
[d
][j
] += d
* 0.5;
541 while ((flip (&d
)) != UP
);
545 see input/regression/dot-up-voice-collision.ly
547 for (vsize i
= 0; i
< clash_groups
[UP
].size (); i
++)
549 Grob
*g
= clash_groups
[UP
][i
];
550 Grob
*dc
= Note_column::dot_column (g
);
553 for (vsize j
= i
+ 1; j
< clash_groups
[UP
].size (); j
++)
555 Grob
*stem
= Note_column::get_stem (clash_groups
[UP
][j
]);
556 Side_position_interface::add_support (dc
, stem
);
561 Check if chords are meshing
564 check_meshing_chords (me
, &offsets
, extents
, clash_groups
);
568 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
569 tups
= scm_cons (scm_cons (clash_groups
[d
][i
]->self_scm (),
570 scm_from_double (offsets
[d
][i
])),
573 while (flip (&d
) != UP
);
579 Note_collision_interface::forced_shift (Grob
*me
)
583 extract_grob_set (me
, "elements", elements
);
584 for (vsize i
= 0; i
< elements
.size (); i
++)
586 Grob
*se
= elements
[i
];
588 SCM force
= se
->get_property ("force-hshift");
589 if (scm_is_number (force
))
590 tups
= scm_cons (scm_cons (se
->self_scm (), force
),
597 Note_collision_interface::add_column (Grob
*me
, Grob
*ncol
)
599 ncol
->set_property ("X-offset", Grob::x_parent_positioning_proc
);
600 Axis_group_interface::add_element (me
, ncol
);
603 ADD_INTERFACE (Note_collision_interface
,
604 "An object that handles collisions between notes with"
605 " different stem directions and horizontal shifts. Most of"
606 " the interesting properties are to be set in"
607 " @ref{note-column-interface}: these are @code{force-hshift}"
608 " and @code{horizontal-shift}.",
611 "merge-differently-dotted "
612 "merge-differently-headed "
614 "prefer-dotted-right "