2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--2012 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"
37 check_meshing_chords (Grob *me,
38 Drul_array<vector<Real> > *offsets,
39 Drul_array<vector<Slice> > const &extents,
40 Drul_array<vector<Grob *> > const &clash_groups)
43 if (!extents[UP].size () || !extents[DOWN].size ())
46 Grob *clash_up = clash_groups[UP][0];
47 Grob *clash_down = clash_groups[DOWN][0];
49 /* Every note column should have a stem, but avoid a crash. */
50 if (!Note_column::get_stem (clash_up) || !Note_column::get_stem (clash_down))
53 Drul_array<Grob *> stems (Note_column::get_stem (clash_down),
54 Note_column::get_stem (clash_up));
56 Grob *head_up = Note_column::first_head (clash_up);
57 Grob *head_down = Note_column::first_head (clash_down);
59 /* Staff-positions of all noteheads on each stem */
60 vector<int> ups = Stem::note_head_positions (stems[UP]);
61 vector<int> dps = Stem::note_head_positions (stems[DOWN]);
63 /* Too far apart to collide. */
64 if (ups[0] > dps.back () + 1)
67 /* If the chords just 'touch' their extreme noteheads,
68 then we can align their stems.
71 if (ups[0] >= dps.back ()
72 && (dps.size () < 2 || ups[0] >= dps[dps.size () - 2] + 2)
73 && (ups.size () < 2 || ups[1] >= dps.back () + 2))
76 /* Filter out the 'o's in this configuration, since they're no
77 * part in the collision.
85 ups = Stem::note_head_positions (stems[UP], true);
86 dps = Stem::note_head_positions (stems[DOWN], true);
88 /* Merge heads if the notes lie the same line, or if the "stem-up-note" is
89 above the "stem-down-note". */
90 bool merge_possible = (ups[0] >= dps[0]) && (ups.back () >= dps.back ());
92 /* Do not merge notes typeset in different style. */
93 if (!ly_is_equal (head_up->get_property ("style"),
94 head_down->get_property ("style")))
95 merge_possible = false;
97 int up_ball_type = Rhythmic_head::duration_log (head_up);
98 int down_ball_type = Rhythmic_head::duration_log (head_down);
100 /* Do not merge whole notes (or longer, like breve, longa, maxima). */
101 if (merge_possible && (up_ball_type <= 0 || down_ball_type <= 0))
102 merge_possible = false;
105 && Rhythmic_head::dot_count (head_up) != Rhythmic_head::dot_count (head_down)
106 && !to_boolean (me->get_property ("merge-differently-dotted")))
107 merge_possible = false;
109 /* Can only merge different heads if merge-differently-headed is set. */
111 && up_ball_type != down_ball_type
112 && !to_boolean (me->get_property ("merge-differently-headed")))
113 merge_possible = false;
115 /* Should never merge quarter and half notes, as this would make
116 them indistinguishable. */
118 && ((Stem::duration_log (stems[UP]) == 1
119 && Stem::duration_log (stems[DOWN]) == 2)
120 || (Stem::duration_log (stems[UP]) == 2
121 && Stem::duration_log (stems[DOWN]) == 1)))
122 merge_possible = false;
125 * this case (distant half collide),
132 * the noteheads may be closer than this case (close half collide)
143 bool close_half_collide = false;
144 bool distant_half_collide = false;
145 bool full_collide = false;
147 for (vsize i = 0, j = 0; i < ups.size () && j < dps.size ();)
149 if (abs (ups[i] - dps[j]) == 1)
151 merge_possible = false;
153 close_half_collide = true;
155 distant_half_collide = true;
157 else if (ups[i] == dps[j])
159 else if (ups[i] > dps[0] && ups[i] < dps.back ())
160 merge_possible = false;
161 else if (dps[j] > ups[0] && dps[j] < ups.back ())
162 merge_possible = false;
166 else if (ups[i] > dps[j])
175 full_collide = full_collide || (close_half_collide
176 && distant_half_collide);
178 /* Determine which chord goes on the left, and which goes right.
179 Up-stem usually goes on the right, but if chords just 'touch' we can put
180 both stems on a common vertical line. In the presense of collisions,
181 right hand heads may obscure dots, so dotted heads to go the right.
183 Real shift_amount = 1;
184 bool stem_to_stem = false;
186 || ((close_half_collide || distant_half_collide)
187 && to_boolean (me->get_property ("prefer-dotted-right"))))
188 && Rhythmic_head::dot_count (head_up) < Rhythmic_head::dot_count (head_down))
192 // remember to leave clearance between stems
197 // Up-stem note on a line has a raised dot, so no risk of collision
198 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
200 || (!Staff_symbol_referencer::on_line (staff, ups[0])
201 && to_boolean (me->get_property ("prefer-dotted-right"))))
202 && Rhythmic_head::dot_count (head_up) > Rhythmic_head::dot_count (head_down))
208 /* The solfa is a triangle, which is inverted depending on stem
209 direction. In case of a collision, one of them should be removed,
210 so the resulting note does not look like a block.
212 SCM up_style = head_up->get_property ("style");
213 SCM down_style = head_down->get_property ("style");
215 && (up_style == ly_symbol2scm ("fa") || up_style == ly_symbol2scm ("faThin"))
216 && (down_style == ly_symbol2scm ("fa") || down_style == ly_symbol2scm ("faThin")))
218 Offset att = Offset (0.0, -1.0);
219 head_up->set_property ("stem-attachment", ly_offset2scm (att));
220 head_up->set_property ("transparent", SCM_BOOL_T);
227 /* If possible, don't wipe any heads. Else, wipe shortest head,
228 or head with smallest amount of dots. Note: when merging
229 different heads, dots on the smaller one disappear. */
231 Grob *dot_wipe_head = head_up;
233 if (up_ball_type == down_ball_type)
235 if (Rhythmic_head::dot_count (head_down) < Rhythmic_head::dot_count (head_up))
237 wipe_ball = head_down;
238 dot_wipe_head = head_down;
240 else if (Rhythmic_head::dot_count (head_down) > Rhythmic_head::dot_count (head_up))
242 dot_wipe_head = head_up;
246 dot_wipe_head = head_up;
248 else if (down_ball_type > up_ball_type)
250 wipe_ball = head_down;
251 dot_wipe_head = head_down;
253 else if (down_ball_type < up_ball_type)
256 dot_wipe_head = head_up;
258 If upper head is eighth note or shorter, and lower head is half note,
259 shift by the difference between the open and filled note head widths,
260 otherwise upper stem will be misaligned slightly.
262 if (Stem::duration_log (stems[DOWN]) == 1
263 && Stem::duration_log (stems[UP]) >= 3)
264 shift_amount = (1 - head_up->extent (head_up, X_AXIS).length ()
265 / head_down->extent (head_down, X_AXIS).length ()) * 0.5;
270 if (Grob *d = unsmob_grob (dot_wipe_head->get_object ("dot")))
274 if (wipe_ball && wipe_ball->is_live ())
275 wipe_ball->set_property ("transparent", SCM_BOOL_T);
277 /* TODO: these numbers are magic; should devise a set of grob props
278 to tune this behavior. */
279 else if (stem_to_stem)
280 shift_amount *= 0.65;
283 else if (close_half_collide)
284 shift_amount *= 0.52;
285 else if (full_collide)
287 else if (distant_half_collide)
291 else if (Rhythmic_head::dot_count (head_up) || Rhythmic_head::dot_count (head_down))
294 shift_amount *= 0.17;
299 && down_ball_type *up_ball_type == 0)
301 if (up_ball_type == 0 && down_ball_type == 1)
302 shift_amount *= 1.25;
303 else if (up_ball_type == 0 && down_ball_type == 2)
304 shift_amount *= 1.35;
305 else if (down_ball_type == 0 && up_ball_type == 1)
307 else if (down_ball_type == 0 && up_ball_type == 2)
308 shift_amount *= 0.75;
311 /* If any dotted notes ended up on the left,
312 tell the Dot_Columnn to avoid the note heads on the right.
314 if (shift_amount < -1e-6
315 && Rhythmic_head::dot_count (head_up))
317 Grob *d = unsmob_grob (head_up->get_object ("dot"));
318 Grob *parent = d->get_parent (X_AXIS);
319 if (Dot_column::has_interface (parent))
320 Side_position_interface::add_support (parent, head_down);
322 else if (Rhythmic_head::dot_count (head_down))
324 Grob *d = unsmob_grob (head_down->get_object ("dot"));
325 Grob *parent = d->get_parent (X_AXIS);
326 if (Dot_column::has_interface (parent))
328 Grob *stem = unsmob_grob (head_up->get_object ("stem"));
329 // Loop over all heads on an up-pointing-stem to see if dots
330 // need to clear any heads suspended on its right side.
331 extract_grob_set (stem, "note-heads", heads);
332 for (vsize i = 0; i < heads.size (); i++)
333 Side_position_interface::add_support (parent, heads[i]);
337 // In meshed chords with dots on the left, adjust dot direction
338 if (shift_amount > 1e-6
339 && Rhythmic_head::dot_count (head_down))
341 Grob *dot_down = unsmob_grob (head_down->get_object ("dot"));
342 Grob *col_down = dot_down->get_parent (X_AXIS);
344 if (Rhythmic_head::dot_count (head_up))
346 Grob *dot_up = unsmob_grob (head_up->get_object ("dot"));
347 Grob *col_up = dot_up->get_parent (X_AXIS);
348 if (col_up == col_down) // let the common DotColumn arrange dots
350 else // conform to the dot direction on the up-stem chord
351 dir = robust_scm2dir (dot_up->get_property ("direction"), UP);
355 Grob *stem = unsmob_grob (head_down->get_object ("stem"));
356 extract_grob_set (stem, "note-heads", heads);
357 for (vsize i = 0; i < heads.size (); i++)
358 unsmob_grob (heads[i]->get_object ("dot"))
359 ->set_property ("direction", scm_from_int (dir));
363 for (UP_and_DOWN (d))
365 for (vsize i = 0; i < clash_groups[d].size (); i++)
366 (*offsets)[d][i] += d * shift_amount;
370 MAKE_SCHEME_CALLBACK (Note_collision_interface, calc_positioning_done, 1)
372 Note_collision_interface::calc_positioning_done (SCM smob)
374 Grob *me = unsmob_grob (smob);
375 me->set_property ("positioning-done", SCM_BOOL_T);
377 Drul_array<vector<Grob *> > clash_groups = get_clash_groups (me);
379 for (UP_and_DOWN (d))
381 for (vsize i = clash_groups[d].size (); i--;)
386 clash_groups[d][i]->extent (me, X_AXIS);
390 SCM autos (automatic_shift (me, clash_groups));
391 SCM hand (forced_shift (me));
394 for (UP_and_DOWN (d))
396 if (clash_groups[d].size ())
398 Grob *h = clash_groups[d][0];
399 Grob *fh = Note_column::first_head (h);
401 wid = fh->extent (h, X_AXIS).length ();
406 Real left_most = 1e6;
408 vector<Real> amounts;
409 for (; scm_is_pair (hand); hand = scm_cdr (hand))
411 Grob *s = unsmob_grob (scm_caar (hand));
412 Real amount = scm_to_double (scm_cdar (hand)) * wid;
415 amounts.push_back (amount);
416 if (amount < left_most)
419 for (; scm_is_pair (autos); autos = scm_cdr (autos))
421 Grob *s = unsmob_grob (scm_caar (autos));
422 Real amount = scm_to_double (scm_cdar (autos)) * wid;
424 vsize x = find (done, s) - done.begin ();
425 if (x == VPOS || x >= done.size ())
428 amounts.push_back (amount);
429 if (amount < left_most)
434 for (vsize i = 0; i < amounts.size (); i++)
435 done[i]->translate_axis (amounts[i] - left_most, X_AXIS);
440 Drul_array < vector<Grob *> >
441 Note_collision_interface::get_clash_groups (Grob *me)
443 Drul_array<vector<Grob *> > clash_groups;
445 extract_grob_set (me, "elements", elements);
446 for (vsize i = 0; i < elements.size (); i++)
448 Grob *se = elements[i];
449 if (Note_column::has_interface (se))
451 if (!Note_column::dir (se))
452 se->programming_error ("note-column has no direction");
454 clash_groups[Note_column::dir (se)].push_back (se);
458 for (UP_and_DOWN (d))
460 vector<Grob *> &clashes (clash_groups[d]);
461 vector_sort (clashes, Note_column::shift_less);
468 This complicated routine moves note columns around horizontally to
469 ensure that notes don't clash.
472 Note_collision_interface::automatic_shift (Grob *me,
473 Drul_array<vector<Grob *> > clash_groups)
475 Drul_array < vector<int> > shifts;
478 for (UP_and_DOWN (d))
480 vector<int> &shift (shifts[d]);
481 vector<Grob *> &clashes (clash_groups[d]);
483 for (vsize i = 0; i < clashes.size (); i++)
486 = clashes[i]->get_property ("horizontal-shift");
488 if (scm_is_number (sh))
489 shift.push_back (scm_to_int (sh));
494 for (vsize i = 1; i < shift.size (); i++)
496 if (shift[i - 1] == shift[i])
498 clashes[0]->warning (_ ("ignoring too many clashing note columns"));
504 Drul_array<vector<Slice> > extents;
505 Drul_array<vector<Real> > offsets;
506 for (UP_and_DOWN (d))
508 for (vsize i = 0; i < clash_groups[d].size (); i++)
510 Slice s (Note_column::head_positions_interval (clash_groups[d][i]));
513 extents[d].push_back (s);
514 offsets[d].push_back (d * 0.5 * i);
519 * do horizontal shifts of each direction
527 for (UP_and_DOWN (d))
529 for (vsize i = 1; i < clash_groups[d].size (); i++)
531 Slice prev = extents[d][i - 1];
532 prev.intersect (extents[d][i]);
533 if (prev.length () > 0
534 || (extents[-d].size () && d * (extents[d][i][-d] - extents[-d][0][d]) < 0))
535 for (vsize j = i; j < clash_groups[d].size (); j++)
536 offsets[d][j] += d * 0.5;
541 see input/regression/dot-up-voice-collision.ly
543 for (vsize i = 0; i < clash_groups[UP].size (); i++)
545 Grob *g = clash_groups[UP][i];
546 Grob *dc = Note_column::dot_column (g);
549 for (vsize j = i + 1; j < clash_groups[UP].size (); j++)
551 Grob *stem = Note_column::get_stem (clash_groups[UP][j]);
552 Side_position_interface::add_support (dc, stem);
557 Check if chords are meshing
560 check_meshing_chords (me, &offsets, extents, clash_groups);
562 for (UP_and_DOWN (d))
564 for (vsize i = 0; i < clash_groups[d].size (); i++)
565 tups = scm_cons (scm_cons (clash_groups[d][i]->self_scm (),
566 scm_from_double (offsets[d][i])),
574 Note_collision_interface::forced_shift (Grob *me)
578 extract_grob_set (me, "elements", elements);
579 for (vsize i = 0; i < elements.size (); i++)
581 Grob *se = elements[i];
583 SCM force = se->get_property ("force-hshift");
584 if (scm_is_number (force))
585 tups = scm_cons (scm_cons (se->self_scm (), force),
592 Note_collision_interface::add_column (Grob *me, Grob *ncol)
594 ncol->set_property ("X-offset", Grob::x_parent_positioning_proc);
595 Axis_group_interface::add_element (me, ncol);
598 ADD_INTERFACE (Note_collision_interface,
599 "An object that handles collisions between notes with"
600 " different stem directions and horizontal shifts. Most of"
601 " the interesting properties are to be set in"
602 " @ref{note-column-interface}: these are @code{force-hshift}"
603 " and @code{horizontal-shift}.",
606 "merge-differently-dotted "
607 "merge-differently-headed "
609 "prefer-dotted-right "