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.
196 && head_up
->get_property ("style") == ly_symbol2scm ("fa")
197 && head_down
->get_property ("style") == ly_symbol2scm ("fa"))
199 Interval uphead_size
= head_up
->extent (head_up
, Y_AXIS
);
200 Offset att
= Offset (0.0, -1.0);
201 head_up
->set_property ("stem-attachment", ly_offset2scm (att
));
202 head_up
->set_property ("transparent", SCM_BOOL_T
);
209 /* If possible, don't wipe any heads. Else, wipe shortest head,
210 or head with smallest amount of dots. Note: when merging
211 different heads, dots on the smaller one disappear. */
213 Grob
*dot_wipe_head
= head_up
;
215 if (up_ball_type
== down_ball_type
)
217 if (Rhythmic_head::dot_count (head_down
) < Rhythmic_head::dot_count (head_up
))
219 wipe_ball
= head_down
;
220 dot_wipe_head
= head_down
;
222 else if (Rhythmic_head::dot_count (head_down
) > Rhythmic_head::dot_count (head_up
))
224 dot_wipe_head
= head_up
;
228 dot_wipe_head
= head_up
;
230 else if (down_ball_type
> up_ball_type
)
232 wipe_ball
= head_down
;
233 dot_wipe_head
= head_down
;
235 else if (down_ball_type
< up_ball_type
)
238 dot_wipe_head
= head_up
;
240 If upper head is eighth note or shorter, and lower head is half note,
241 shift by the difference between the open and filled note head widths,
242 otherwise upper stem will be misaligned slightly.
244 if (Stem::duration_log (stems
[DOWN
]) == 1
245 && Stem::duration_log (stems
[UP
]) >= 3)
246 shift_amount
= (1 - head_up
->extent (head_up
, X_AXIS
).length () /
247 head_down
->extent (head_down
, X_AXIS
).length ()) * 0.5;
252 if (Grob
*d
= unsmob_grob (dot_wipe_head
->get_object ("dot")))
256 if (wipe_ball
&& wipe_ball
->is_live ())
257 wipe_ball
->set_property ("transparent", SCM_BOOL_T
);
259 /* TODO: these numbers are magic; should devise a set of grob props
260 to tune this behavior. */
261 else if (stem_to_stem
)
262 shift_amount
= -abs (shift_amount
) * 0.65;
263 else if (close_half_collide
&& !touch
)
264 shift_amount
*= 0.52;
265 else if (distant_half_collide
&& !touch
)
267 else if (distant_half_collide
|| close_half_collide
|| full_collide
)
271 else if (Rhythmic_head::dot_count (head_up
) || Rhythmic_head::dot_count (head_down
))
274 shift_amount
*= 0.17;
280 && down_ball_type
* up_ball_type
== 0)
282 if (up_ball_type
== 0 && down_ball_type
== 1)
283 shift_amount
*= 1.25;
284 else if (up_ball_type
== 0 && down_ball_type
== 2)
285 shift_amount
*= 1.35;
286 else if (down_ball_type
== 0 && up_ball_type
== 1)
288 else if (down_ball_type
== 0 && up_ball_type
== 2)
289 shift_amount
*= 0.75;
295 * Dots from left note head collide with right note head. Only occurs
296 * with a close half collide, if the left note head is between
297 * lines and the right note head is on a line, and if right note head
298 * hasn't got any dots.
300 if (close_half_collide
301 && Rhythmic_head::dot_count (head_up
)
302 && !Rhythmic_head::dot_count (head_down
))
304 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
305 if (!Staff_symbol_referencer::on_line (staff
, ups
[0]))
308 TODO: consider junking the else body.
310 if (to_boolean (me
->get_property ("prefer-dotted-right")))
314 Grob
*d
= unsmob_grob (head_up
->get_object ("dot"));
315 Grob
*parent
= d
->get_parent (X_AXIS
);
316 if (Dot_column::has_interface (parent
))
317 Side_position_interface::add_support (parent
, head_down
);
322 /* For full or close half collisions, the right hand head may
323 obscure dots. Move dots to the right. */
324 if (abs (shift_amount
) > 1e-6
325 && Rhythmic_head::dot_count (head_down
) > Rhythmic_head::dot_count (head_up
)
326 && (full_collide
|| close_half_collide
))
328 Grob
*d
= unsmob_grob (head_down
->get_object ("dot"));
329 Grob
*parent
= d
->get_parent (X_AXIS
);
339 the . is put right of o which is erroneous o force-shifted
342 if (Dot_column::has_interface (parent
))
344 Grob
*stem
= unsmob_grob (head_up
->get_object ("stem"));
345 extract_grob_set (stem
, "note-heads", heads
);
346 for (vsize i
= 0; i
< heads
.size (); i
++)
347 Side_position_interface::add_support (parent
, heads
[i
]);
354 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
355 (*offsets
)[d
][i
] += d
* shift_amount
;
357 while ((flip (&d
)) != UP
);
361 MAKE_SCHEME_CALLBACK (Note_collision_interface
, calc_positioning_done
, 1)
363 Note_collision_interface::calc_positioning_done (SCM smob
)
365 Grob
*me
= unsmob_grob (smob
);
366 me
->set_property ("positioning-done", SCM_BOOL_T
);
368 Drul_array
<vector
<Grob
*> > clash_groups
= get_clash_groups (me
);
373 for (vsize i
= clash_groups
[d
].size (); i
--; )
378 clash_groups
[d
][i
]->extent (me
, X_AXIS
);
381 while (flip (&d
) != UP
);
383 SCM
autos (automatic_shift (me
, clash_groups
));
384 SCM
hand (forced_shift (me
));
389 if (clash_groups
[d
].size ())
391 Grob
*h
= clash_groups
[d
][0];
392 Grob
*fh
= Note_column::first_head (h
);
394 wid
= fh
->extent (h
, X_AXIS
).length ();
397 while (flip (&d
) != UP
);
400 Real left_most
= 1e6
;
402 vector
<Real
> amounts
;
403 for (; scm_is_pair (hand
); hand
= scm_cdr (hand
))
405 Grob
*s
= unsmob_grob (scm_caar (hand
));
406 Real amount
= scm_to_double (scm_cdar (hand
)) * wid
;
409 amounts
.push_back (amount
);
410 if (amount
< left_most
)
413 for (; scm_is_pair (autos
); autos
= scm_cdr (autos
))
415 Grob
*s
= unsmob_grob (scm_caar (autos
));
416 Real amount
= scm_to_double (scm_cdar (autos
)) * wid
;
418 vsize x
= find (done
, s
) - done
.begin ();
419 if (x
== VPOS
|| x
>= done
.size ())
422 amounts
.push_back (amount
);
423 if (amount
< left_most
)
428 for (vsize i
= 0; i
< amounts
.size (); i
++)
429 done
[i
]->translate_axis (amounts
[i
] - left_most
, X_AXIS
);
434 Drul_array
< vector
<Grob
*> >
435 Note_collision_interface::get_clash_groups (Grob
*me
)
437 Drul_array
<vector
<Grob
*> > clash_groups
;
439 extract_grob_set (me
, "elements", elements
);
440 for (vsize i
= 0; i
< elements
.size (); i
++)
442 Grob
*se
= elements
[i
];
443 if (Note_column::has_interface (se
))
445 if (!Note_column::dir (se
))
446 se
->programming_error ("note-column has no direction");
448 clash_groups
[Note_column::dir (se
)].push_back (se
);
455 vector
<Grob
*> &clashes (clash_groups
[d
]);
456 vector_sort (clashes
, Note_column::shift_less
);
458 while ((flip (&d
)) != UP
);
464 This complicated routine moves note columns around horizontally to
465 ensure that notes don't clash.
468 Note_collision_interface::automatic_shift (Grob
*me
,
469 Drul_array
<vector
<Grob
*> > clash_groups
)
471 Drul_array
< vector
<int> > shifts
;
477 vector
<int> &shift (shifts
[d
]);
478 vector
<Grob
*> &clashes (clash_groups
[d
]);
480 for (vsize i
= 0; i
< clashes
.size (); i
++)
483 = clashes
[i
]->get_property ("horizontal-shift");
485 if (scm_is_number (sh
))
486 shift
.push_back (scm_to_int (sh
));
491 for (vsize i
= 1; i
< shift
.size (); i
++)
493 if (shift
[i
- 1] == shift
[i
])
495 clashes
[0]->warning (_ ("ignoring too many clashing note columns"));
500 while ((flip (&d
)) != UP
);
502 Drul_array
<vector
<Slice
> > extents
;
503 Drul_array
<vector
<Real
> > offsets
;
507 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
509 Slice
s (Note_column::head_positions_interval (clash_groups
[d
][i
]));
512 extents
[d
].push_back (s
);
513 offsets
[d
].push_back (d
* 0.5 * i
);
516 while ((flip (&d
)) != UP
);
519 do horizontal shifts of each direction
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;
539 while ((flip (&d
)) != UP
);
543 see input/regression/dot-up-voice-collision.ly
545 for (vsize i
= 0; i
< clash_groups
[UP
].size (); i
++)
547 Grob
*g
= clash_groups
[UP
][i
];
548 Grob
*dc
= Note_column::dot_column (g
);
551 for (vsize j
= i
+ 1; j
< clash_groups
[UP
].size (); j
++)
553 Grob
*stem
= Note_column::get_stem (clash_groups
[UP
][j
]);
554 Side_position_interface::add_support (dc
, stem
);
559 Check if chords are meshing
562 check_meshing_chords (me
, &offsets
, extents
, clash_groups
);
566 for (vsize i
= 0; i
< clash_groups
[d
].size (); i
++)
567 tups
= scm_cons (scm_cons (clash_groups
[d
][i
]->self_scm (),
568 scm_from_double (offsets
[d
][i
])),
571 while (flip (&d
) != UP
);
577 Note_collision_interface::forced_shift (Grob
*me
)
581 extract_grob_set (me
, "elements", elements
);
582 for (vsize i
= 0; i
< elements
.size (); i
++)
584 Grob
*se
= elements
[i
];
586 SCM force
= se
->get_property ("force-hshift");
587 if (scm_is_number (force
))
588 tups
= scm_cons (scm_cons (se
->self_scm (), force
),
595 Note_collision_interface::add_column (Grob
*me
, Grob
*ncol
)
597 ncol
->set_property ("X-offset", Grob::x_parent_positioning_proc
);
598 Axis_group_interface::add_element (me
, ncol
);
601 ADD_INTERFACE (Note_collision_interface
,
602 "An object that handles collisions between notes with"
603 " different stem directions and horizontal shifts. Most of"
604 " the interesting properties are to be set in"
605 " @ref{note-column-interface}: these are @code{force-hshift}"
606 " and @code{horizontal-shift}.",
609 "merge-differently-dotted "
610 "merge-differently-headed "
612 "prefer-dotted-right "