2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1996--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
5 Jan Nieuwenhuizen <janneke@gnu.org>
7 LilyPond is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 LilyPond is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
22 #include "slur-scoring.hh"
26 #include "accidental-interface.hh"
28 #include "directional-element-interface.hh"
29 #include "libc-extension.hh"
31 #include "note-column.hh"
32 #include "output-def.hh"
33 #include "paper-column.hh"
35 #include "pointer-group-interface.hh"
36 #include "slur-configuration.hh"
39 #include "staff-symbol-referencer.hh"
40 #include "staff-symbol.hh"
47 - curve around flag for y coordinate
49 - short-cut: try a smaller region first.
51 - handle non-visible stems better.
53 - try to prune number of scoring criteria
55 - take encompass-objects more into account when determining
58 - calculate encompass scoring directly after determining slur shape.
62 struct Slur_score_state
;
64 Slur_score_state::Slur_score_state ()
68 edge_has_beams_
= false;
69 has_same_beam_
= false;
77 Slur_score_state::~Slur_score_state ()
79 junk_pointers (configurations_
);
83 If a slur is broken across a line break, the direction
84 of the post-break slur must be the same as the pre-break
88 Slur_score_state::slur_direction () const
90 Grob
*left_neighbor
= slur_
->broken_neighbor (LEFT
);
92 if (left_neighbor
&& left_neighbor
->is_live ())
93 return get_grob_direction (left_neighbor
);
95 Direction dir
= get_grob_direction (slur_
);
97 if (Grob
*right_neighbor
= slur_
->broken_neighbor (RIGHT
))
98 set_grob_direction (right_neighbor
, dir
);
104 Slur_score_state::get_encompass_info (Grob
*col
) const
106 Grob
*stem
= unsmob_grob (col
->get_object ("stem"));
111 programming_error ("no stem for note column");
112 ei
.x_
= col
->relative_coordinate (common_
[X_AXIS
], X_AXIS
);
113 ei
.head_
= ei
.stem_
= col
->extent (common_
[Y_AXIS
],
117 Direction stem_dir
= get_grob_direction (stem
);
119 if (Grob
*head
= Note_column::first_head (col
))
120 ei
.x_
= head
->extent (common_
[X_AXIS
], X_AXIS
).center ();
122 ei
.x_
= col
->extent (common_
[X_AXIS
], X_AXIS
).center ();
124 Grob
*h
= Stem::extremal_heads (stem
)[Direction (dir_
)];
127 ei
.head_
= ei
.stem_
= col
->extent (common_
[Y_AXIS
], Y_AXIS
)[dir_
];
131 ei
.head_
= h
->extent (common_
[Y_AXIS
], Y_AXIS
)[dir_
];
133 if ((stem_dir
== dir_
)
134 && !stem
->extent (stem
, Y_AXIS
).is_empty ())
136 ei
.stem_
= stem
->extent (common_
[Y_AXIS
], Y_AXIS
)[dir_
];
137 if (Grob
*b
= Stem::get_beam (stem
))
138 ei
.stem_
+= stem_dir
* 0.5 * Beam::get_beam_thickness (b
);
140 Interval x
= stem
->extent (common_
[X_AXIS
], X_AXIS
);
141 ei
.x_
= x
.is_empty ()
142 ? stem
->relative_coordinate (common_
[X_AXIS
], X_AXIS
)
151 Drul_array
<Bound_info
>
152 Slur_score_state::get_bound_info () const
154 Drul_array
<Bound_info
> extremes
;
157 Direction dir
= dir_
;
161 extremes
[d
].bound_
= slur_
->get_bound (d
);
162 if (Note_column::has_interface (extremes
[d
].bound_
))
164 extremes
[d
].note_column_
= extremes
[d
].bound_
;
165 extremes
[d
].stem_
= Note_column::get_stem (extremes
[d
].note_column_
);
166 if (extremes
[d
].stem_
)
168 extremes
[d
].stem_dir_
= get_grob_direction (extremes
[d
].stem_
);
170 for (int a
= X_AXIS
; a
< NO_AXES
; a
++)
173 Interval s
= extremes
[d
].stem_
->extent (common_
[ax
], ax
);
177 do not issue warning. This happens for rests and
181 + extremes
[d
].stem_
->relative_coordinate (common_
[ax
], ax
);
183 extremes
[d
].stem_extent_
[ax
] = s
;
186 extremes
[d
].slur_head_
187 = Stem::extremal_heads (extremes
[d
].stem_
)[dir
];
188 if (!extremes
[d
].slur_head_
189 && Note_column::has_rests (extremes
[d
].bound_
))
190 extremes
[d
].slur_head_
= Note_column::get_rest (extremes
[d
].bound_
);
191 extremes
[d
].staff_
= Staff_symbol_referencer
192 ::get_staff_symbol (extremes
[d
].stem_
);
193 extremes
[d
].staff_space_
= Staff_symbol_referencer
194 ::staff_space (extremes
[d
].stem_
);
197 if (extremes
[d
].slur_head_
)
198 extremes
[d
].slur_head_x_extent_
199 = extremes
[d
].slur_head_
->extent (common_
[X_AXIS
], X_AXIS
);
203 while (flip (&d
) != LEFT
);
209 Slur_score_state::fill (Grob
*me
)
211 slur_
= dynamic_cast<Spanner
*> (me
);
213 = internal_extract_grob_array (me
, ly_symbol2scm ("note-columns"));
215 if (columns_
.empty ())
221 Slur::replace_breakable_encompass_objects (me
);
222 staff_space_
= Staff_symbol_referencer::staff_space (me
);
223 Real lt
= me
->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
224 thickness_
= robust_scm2double (me
->get_property ("thickness"), 1.0) * lt
;
226 dir_
= slur_direction ();
227 parameters_
.fill (me
);
229 extract_grob_set (me
, "note-columns", columns
);
230 extract_grob_set (me
, "encompass-objects", extra_objects
);
232 Spanner
*sp
= dynamic_cast<Spanner
*> (me
);
234 for (int i
= X_AXIS
; i
< NO_AXES
; i
++)
237 common_
[a
] = common_refpoint_of_array (columns
, me
, a
);
238 common_
[a
] = common_refpoint_of_array (extra_objects
, common_
[a
], a
);
244 If bound is not in note-columns, we don't want to know about
248 common_
[a
] = common_
[a
]->common_refpoint (sp
->get_bound (d
), a
);
250 while (flip (&d
) != LEFT
);
253 extremes_
= get_bound_info ();
254 is_broken_
= (!extremes_
[LEFT
].note_column_
255 || !extremes_
[RIGHT
].note_column_
);
258 = (extremes_
[LEFT
].stem_
&& extremes_
[RIGHT
].stem_
259 && Stem::get_beam (extremes_
[LEFT
].stem_
) == Stem::get_beam (extremes_
[RIGHT
].stem_
));
261 base_attachments_
= get_base_attachments ();
263 Drul_array
<Real
> end_ys
264 = get_y_attachment_range ();
266 configurations_
= enumerate_attachments (end_ys
);
267 for (vsize i
= 0; i
< columns_
.size (); i
++)
268 encompass_infos_
.push_back (get_encompass_info (columns_
[i
]));
270 extra_encompass_infos_
= get_extra_encompass_infos ();
278 && extremes_
[d
].slur_head_
)
280 * extremes_
[d
].slur_head_
->relative_coordinate (common_
[Y_AXIS
], Y_AXIS
);
282 while (flip (&d
) != LEFT
);
285 = (extremes_
[LEFT
].stem_
&& Stem::get_beam (extremes_
[LEFT
].stem_
))
286 || (extremes_
[RIGHT
].stem_
&& Stem::get_beam (extremes_
[RIGHT
].stem_
));
293 MAKE_SCHEME_CALLBACK (Slur
, calc_control_points
, 1)
295 Slur::calc_control_points (SCM smob
)
297 Spanner
*me
= unsmob_spanner (smob
);
299 Slur_score_state state
;
305 state
.generate_curves ();
307 SCM end_ys
= me
->get_property ("positions");
308 SCM inspect_quants
= me
->get_property ("inspect-quants");
309 bool debug_slurs
= to_boolean (me
->layout ()
310 ->lookup_variable (ly_symbol2scm ("debug-slur-scoring")));
312 if (is_number_pair (inspect_quants
))
315 end_ys
= inspect_quants
;
318 Slur_configuration
*best
= NULL
;
319 if (is_number_pair (end_ys
))
320 best
= state
.get_forced_configuration (ly_scm2interval(end_ys
));
322 best
= state
.get_best_curve ();
324 #if DEBUG_SLUR_SCORING
327 string total
= best
->card ();
328 total
+= to_string (" TOTAL=%.2f idx=%d", best
->score (), best
->index_
);
329 me
->set_property ("annotation", ly_string2scm (total
));
333 SCM controls
= SCM_EOL
;
334 for (int i
= 4; i
--;)
336 Offset o
= best
->curve_
.control_
[i
]
337 - Offset (me
->relative_coordinate (state
.common_
[X_AXIS
], X_AXIS
),
338 me
->relative_coordinate (state
.common_
[Y_AXIS
], Y_AXIS
));
339 controls
= scm_cons (ly_offset2scm (o
), controls
);
346 Slur_score_state::get_forced_configuration (Interval ys
) const
348 Slur_configuration
*best
= NULL
;
350 for (vsize i
= 0; i
< configurations_
.size (); i
++)
352 Real d
= fabs (configurations_
[i
]->attachment_
[LEFT
][Y_AXIS
] - ys
[LEFT
])
353 + fabs (configurations_
[i
]->attachment_
[RIGHT
][Y_AXIS
] - ys
[RIGHT
]);
356 best
= configurations_
[i
];
361 while (!best
->done ())
362 best
->run_next_scorer (*this);
365 programming_error ("cannot find quant");
372 Slur_score_state::get_best_curve () const
374 std::priority_queue
<Slur_configuration
*, std::vector
<Slur_configuration
*>,
375 Slur_configuration_less
> queue
;
376 for (vsize i
= 0; i
< configurations_
.size (); i
++)
377 queue
.push (configurations_
[i
]);
379 Slur_configuration
*best
= NULL
;
386 best
->run_next_scorer (*this);
394 Slur_score_state::breakable_bound_item (Direction d
) const
396 Grob
*col
= slur_
->get_bound (d
)->get_column ();
398 extract_grob_set (slur_
, "encompass-objects", extra_encompasses
);
400 for (vsize i
= 0; i
< extra_encompasses
.size (); i
++)
402 Item
*item
= dynamic_cast<Item
*> (extra_encompasses
[i
]);
403 if (item
&& col
== item
->get_column ())
411 TODO: should analyse encompasses to determine sensible region, and
412 should limit slopes available.
416 Slur_score_state::get_y_attachment_range () const
418 Drul_array
<Real
> end_ys
;
422 if (extremes_
[d
].note_column_
)
425 * max (max (dir_
* (base_attachments_
[d
][Y_AXIS
]
426 + parameters_
.region_size_
* dir_
),
427 dir_
* (dir_
+ extremes_
[d
].note_column_
->extent (common_
[Y_AXIS
], Y_AXIS
)[dir_
])),
428 dir_
* base_attachments_
[-d
][Y_AXIS
]);
431 end_ys
[d
] = base_attachments_
[d
][Y_AXIS
] + parameters_
.region_size_
* dir_
;
433 while (flip (&d
) != LEFT
);
439 spanner_less (Spanner
*s1
, Spanner
*s2
)
445 b1
[d
] = s1
->get_bound (d
)->get_column ()->get_rank ();
446 b2
[d
] = s2
->get_bound (d
)->get_column ()->get_rank ();
448 while (flip (&d
) != LEFT
);
450 return b2
[LEFT
] <= b1
[LEFT
] && b2
[RIGHT
] >= b1
[RIGHT
]
451 && (b2
[LEFT
] != b1
[LEFT
] || b2
[RIGHT
] != b1
[RIGHT
]);
455 Slur_score_state::get_base_attachments () const
457 Drul_array
<Offset
> base_attachment
;
461 Grob
*stem
= extremes_
[d
].stem_
;
462 Grob
*head
= extremes_
[d
].slur_head_
;
466 if (extremes_
[d
].note_column_
)
470 fixme: X coord should also be set in this case.
473 && !Stem::is_invisible (stem
)
474 && extremes_
[d
].stem_dir_
== dir_
475 && Stem::get_beaming (stem
, -d
)
476 && Stem::get_beam (stem
)
477 && (!spanner_less (slur_
, Stem::get_beam (stem
))
479 y
= extremes_
[d
].stem_extent_
[Y_AXIS
][dir_
];
481 y
= head
->extent (common_
[Y_AXIS
], Y_AXIS
)[dir_
];
482 y
+= dir_
* 0.5 * staff_space_
;
484 y
= move_away_from_staffline (y
, head
);
486 Grob
*fh
= Note_column::first_head (extremes_
[d
].note_column_
);
488 = (fh
? fh
->extent (common_
[X_AXIS
], X_AXIS
)
489 : extremes_
[d
].bound_
->extent (common_
[X_AXIS
], X_AXIS
))
490 .linear_combination (CENTER
);
492 base_attachment
[d
] = Offset (x
, y
);
494 while (flip (&d
) != LEFT
);
498 if (!extremes_
[d
].note_column_
)
503 if (Grob
*g
= breakable_bound_item (d
))
505 x
= robust_relative_extent (g
, common_
[X_AXIS
], X_AXIS
)[RIGHT
];
508 x
= robust_relative_extent (extremes_
[d
].bound_
, common_
[X_AXIS
], X_AXIS
)[d
];
510 x
= slur_
->get_broken_left_end_align ();
512 Grob
*col
= (d
== LEFT
) ? columns_
[0] : columns_
.back ();
514 if (extremes_
[-d
].bound_
!= col
)
516 y
= robust_relative_extent (col
, common_
[Y_AXIS
], Y_AXIS
)[dir_
];
517 y
+= dir_
* 0.5 * staff_space_
;
519 if (get_grob_direction (col
) == dir_
520 && Note_column::get_stem (col
)
521 && !Stem::is_invisible (Note_column::get_stem (col
)))
522 y
-= dir_
* 1.5 * staff_space_
;
525 y
= base_attachment
[-d
][Y_AXIS
];
527 y
= move_away_from_staffline (y
, col
);
529 base_attachment
[d
] = Offset (x
, y
);
532 while (flip (&d
) != LEFT
);
536 for (int a
= X_AXIS
; a
< NO_AXES
; a
++)
538 Real
&b
= base_attachment
[d
][Axis (a
)];
540 if (isinf (b
) || isnan (b
))
542 programming_error ("slur attachment is inf/nan");
547 while (flip (&d
) != LEFT
);
549 return base_attachment
;
553 Slur_score_state::move_away_from_staffline (Real y
,
554 Grob
*on_staff
) const
559 Grob
*staff_symbol
= Staff_symbol_referencer::get_staff_symbol (on_staff
);
564 = (y
- staff_symbol
->relative_coordinate (common_
[Y_AXIS
],
566 * 2.0 / staff_space_
;
568 if (fabs (pos
- my_round (pos
)) < 0.2
569 && Staff_symbol_referencer::on_line (on_staff
, (int) rint (pos
))
570 && Staff_symbol_referencer::line_count (on_staff
) - 1 >= rint (pos
))
571 y
+= 1.5 * staff_space_
* dir_
/ 10;
577 Slur_score_state::generate_avoid_offsets () const
579 vector
<Offset
> avoid
;
580 vector
<Grob
*> encompasses
= columns_
;
582 for (vsize i
= 0; i
< encompasses
.size (); i
++)
584 if (extremes_
[LEFT
].note_column_
== encompasses
[i
]
585 || extremes_
[RIGHT
].note_column_
== encompasses
[i
])
588 Encompass_info
inf (get_encompass_info (encompasses
[i
]));
589 Real y
= dir_
* (max (dir_
* inf
.head_
, dir_
* inf
.stem_
));
591 avoid
.push_back (Offset (inf
.x_
, y
+ dir_
* parameters_
.free_head_distance_
));
594 extract_grob_set (slur_
, "encompass-objects", extra_encompasses
);
595 for (vsize i
= 0; i
< extra_encompasses
.size (); i
++)
597 if (Slur::has_interface (extra_encompasses
[i
]))
599 Grob
*small_slur
= extra_encompasses
[i
];
600 Bezier b
= Slur::get_curve (small_slur
);
602 Offset z
= b
.curve_point (0.5);
603 z
+= Offset (small_slur
->relative_coordinate (common_
[X_AXIS
], X_AXIS
),
604 small_slur
->relative_coordinate (common_
[Y_AXIS
], Y_AXIS
));
606 z
[Y_AXIS
] += dir_
* parameters_
.free_slur_distance_
;
609 else if (extra_encompasses
[i
]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
611 Grob
*g
= extra_encompasses
[i
];
612 Interval xe
= g
->extent (common_
[X_AXIS
], X_AXIS
);
613 Interval ye
= g
->extent (common_
[Y_AXIS
], Y_AXIS
);
617 avoid
.push_back (Offset (xe
.center (), ye
[dir_
]));
624 Slur_score_state::generate_curves () const
626 Real r_0
= robust_scm2double (slur_
->get_property ("ratio"), 0.33);
627 Real h_inf
= staff_space_
* scm_to_double (slur_
->get_property ("height-limit"));
629 vector
<Offset
> avoid
= generate_avoid_offsets ();
630 for (vsize i
= 0; i
< configurations_
.size (); i
++)
631 configurations_
[i
]->generate_curve (*this, r_0
, h_inf
, avoid
);
634 vector
<Slur_configuration
*>
635 Slur_score_state::enumerate_attachments (Drul_array
<Real
> end_ys
) const
637 vector
<Slur_configuration
*> scores
;
639 Drul_array
<Offset
> os
;
640 os
[LEFT
] = base_attachments_
[LEFT
];
641 Real minimum_length
= staff_space_
642 * robust_scm2double (slur_
->get_property ("minimum-length"), 2.0);
644 for (int i
= 0; dir_
* os
[LEFT
][Y_AXIS
] <= dir_
* end_ys
[LEFT
]; i
++)
646 os
[RIGHT
] = base_attachments_
[RIGHT
];
647 for (int j
= 0; dir_
* os
[RIGHT
][Y_AXIS
] <= dir_
* end_ys
[RIGHT
]; j
++)
650 Drul_array
<bool> attach_to_stem (false, false);
653 os
[d
][X_AXIS
] = base_attachments_
[d
][X_AXIS
];
654 if (extremes_
[d
].stem_
655 && !Stem::is_invisible (extremes_
[d
].stem_
)
656 && extremes_
[d
].stem_dir_
== dir_
)
658 Interval stem_y
= extremes_
[d
].stem_extent_
[Y_AXIS
];
659 stem_y
.widen (0.25 * staff_space_
);
660 if (stem_y
.contains (os
[d
][Y_AXIS
]))
662 os
[d
][X_AXIS
] = extremes_
[d
].stem_extent_
[X_AXIS
][-d
]
664 attach_to_stem
[d
] = true;
666 else if (dir_
* extremes_
[d
].stem_extent_
[Y_AXIS
][dir_
]
667 < dir_
* os
[d
][Y_AXIS
]
668 && !extremes_
[d
].stem_extent_
[X_AXIS
].is_empty ())
670 os
[d
][X_AXIS
] = extremes_
[d
].stem_extent_
[X_AXIS
].center ();
673 while (flip (&d
) != LEFT
);
676 dz
= os
[RIGHT
] - os
[LEFT
];
677 if (dz
[X_AXIS
] < minimum_length
678 || fabs (dz
[Y_AXIS
] / dz
[X_AXIS
]) > parameters_
.max_slope_
)
682 if (extremes_
[d
].slur_head_
683 && !extremes_
[d
].slur_head_x_extent_
.is_empty ())
685 os
[d
][X_AXIS
] = extremes_
[d
].slur_head_x_extent_
.center ();
686 attach_to_stem
[d
] = false;
689 while (flip (&d
) != LEFT
);
692 dz
= os
[RIGHT
] - os
[LEFT
];
695 if (extremes_
[d
].slur_head_
696 && !attach_to_stem
[d
])
698 /* Horizontally move tilted slurs a little. Move
699 more for bigger tilts.
703 -= dir_
* extremes_
[d
].slur_head_x_extent_
.length ()
704 * sin (dz
.arg ()) / 3;
707 while (flip (&d
) != LEFT
);
709 scores
.push_back (Slur_configuration::new_config (os
, scores
.size ()));
711 os
[RIGHT
][Y_AXIS
] += dir_
* staff_space_
/ 2;
714 os
[LEFT
][Y_AXIS
] += dir_
* staff_space_
/ 2;
717 assert (scores
.size () > 0);
721 vector
<Extra_collision_info
>
722 Slur_score_state::get_extra_encompass_infos () const
724 extract_grob_set (slur_
, "encompass-objects", encompasses
);
725 vector
<Extra_collision_info
> collision_infos
;
726 for (vsize i
= encompasses
.size (); i
--;)
728 if (Slur::has_interface (encompasses
[i
]))
730 Spanner
*small_slur
= dynamic_cast<Spanner
*> (encompasses
[i
]);
731 Bezier b
= Slur::get_curve (small_slur
);
733 Offset
relative (small_slur
->relative_coordinate (common_
[X_AXIS
], X_AXIS
),
734 small_slur
->relative_coordinate (common_
[Y_AXIS
], Y_AXIS
));
736 for (int k
= 0; k
< 3; k
++)
738 Direction hdir
= Direction (k
- 1);
741 Only take bound into account if small slur starts
742 together with big slur.
744 if (hdir
&& small_slur
->get_bound (hdir
) != slur_
->get_bound (hdir
))
747 Offset z
= b
.curve_point (k
/ 2.0);
752 yext
[dir_
] = z
[Y_AXIS
] + dir_
* thickness_
* 1.0;
754 Interval
xext (-1, 1);
755 xext
= xext
* (thickness_
* 2) + z
[X_AXIS
];
756 Extra_collision_info
info (small_slur
,
760 parameters_
.extra_object_collision_penalty_
);
761 collision_infos
.push_back (info
);
766 Grob
*g
= encompasses
[i
];
767 Interval xe
= g
->extent (common_
[X_AXIS
], X_AXIS
);
768 Interval ye
= g
->extent (common_
[Y_AXIS
], Y_AXIS
);
771 Real penalty
= parameters_
.extra_object_collision_penalty_
;
772 if (Accidental_interface::has_interface (g
))
774 penalty
= parameters_
.accidental_collision_
;
776 Rational alt
= ly_scm2rational (g
->get_property ("alteration"));
777 SCM scm_style
= g
->get_property ("style");
778 if (!scm_is_symbol (scm_style
)
779 && !to_boolean (g
->get_property ("parenthesized"))
780 && !to_boolean (g
->get_property ("restore-first")))
782 /* End copy accidental.cc */
783 if (alt
== FLAT_ALTERATION
784 || alt
== DOUBLE_FLAT_ALTERATION
)
786 else if (alt
== SHARP_ALTERATION
)
788 else if (alt
== NATURAL_ALTERATION
)
793 ye
.widen (thickness_
* 0.5);
794 xe
.widen (thickness_
* 1.0);
795 Extra_collision_info
info (g
, xp
, xe
, ye
, penalty
);
796 collision_infos
.push_back (info
);
800 return collision_infos
;
803 Extra_collision_info::Extra_collision_info (Grob
*g
, Real idx
, Interval x
, Interval y
, Real p
)
806 extents_
[X_AXIS
] = x
;
807 extents_
[Y_AXIS
] = y
;
810 type_
= g
->get_property ("avoid-slur");
813 Extra_collision_info::Extra_collision_info ()