2 slur-quanting.cc -- Score based slur formatting
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
12 #include "accidental-interface.hh"
14 #include "directional-element-interface.hh"
15 #include "group-interface.hh"
16 #include "libc-extension.hh"
17 #include "lily-guile.hh"
19 #include "note-column.hh"
20 #include "output-def.hh"
24 #include "staff-symbol-referencer.hh"
25 #include "staff-symbol.hh"
28 #include "paper-column.hh"
33 - curve around flag for y coordinate
35 - this file is a big mess, clean it up
38 - short-cut: try a smaller region first.
40 - handle non-visible stems better.
42 - try to prune number of scoring criteria
44 - take encompass-objects more into account when determining
47 - calculate encompass scoring directly after determining slur shape.
55 Drul_array<Offset> attachment_;
59 #if DEBUG_SLUR_QUANTING
69 struct Slur_score_parameters
72 Real head_encompass_penalty_;
73 Real stem_encompass_penalty_;
74 Real closeness_factor_;
75 Real edge_attraction_factor_;
76 Real same_slope_penalty_;
77 Real steeper_slope_factor_;
78 Real non_horizontal_penalty_;
80 Real max_slope_factor_;
81 Real extra_object_collision_;
82 Real accidental_collision_;
83 Real free_slur_distance_;
84 Real free_head_distance_;
85 Real extra_encompass_free_distance_;
86 Real edge_slope_exponent_;
87 Real head_slur_distance_max_ratio_;
88 Real head_slur_distance_factor_;
92 Slur_score_parameters (Grob*);
107 Real get_point (Direction dir)
125 Interval slur_head_extent_;
141 TODO: create one object for passing all parameters.
146 score_extra_encompass (Grob *me, Grob *common[],
147 Slur_score_parameters *score_param,
148 Drul_array<Bound_info> ,
150 Array<Slur_score> * scores);
151 static void score_slopes (Grob *me, Grob *common[],
152 Slur_score_parameters *score_param,
153 Drul_array<Bound_info>,
154 Drul_array<Offset> base_attach,
155 Array<Slur_score> *scores);
157 static void score_edges (Grob *me, Grob *common[],
158 Slur_score_parameters *score_param,
159 Drul_array<Bound_info> extremes,
160 Drul_array<Offset> base_attach,
161 Array<Slur_score> *scores);
162 static void score_encompass (Grob *me, Grob *common[],
163 Slur_score_parameters*,
164 Drul_array<Bound_info>,
165 Drul_array<Offset>, Array<Slur_score> *scores);
166 static Bezier avoid_staff_line (Grob *me, Grob **common,
167 Drul_array<Bound_info> extremes,
170 static Encompass_info get_encompass_info (Grob *me,
173 static Bezier get_bezier (Grob *me,
175 Slur_score_parameters*,
176 Drul_array<Bound_info> extremes,
177 Drul_array<Offset> attachments,
178 Real r_0, Real h_inf);
179 static Direction get_default_dir (Grob *me);
181 static void set_end_points (Grob *);
182 static Real broken_trend_y (Grob *me, Grob **, Direction dir);
183 static Drul_array<Bound_info> get_bound_info (Spanner *me, Grob **common);
185 static void generate_curves (Grob *me,
186 Grob *common[],Slur_score_parameters*,
187 Drul_array<Bound_info> extremes,
188 Drul_array<Offset> base_attach,
189 Array<Slur_score> *scores);
190 static Array<Slur_score> enumerate_attachments
191 (Grob *me, Grob *common[], Slur_score_parameters*,
192 Drul_array<Bound_info> extremes,
193 Drul_array<Offset> base_attachment, Drul_array<Real> end_ys);
194 static Drul_array<Offset> get_base_attachments
195 (Spanner *sp, Grob **common, Drul_array<Bound_info> extremes);
196 static Drul_array<Real> get_y_attachment_range
197 (Spanner *sp, Grob **common,
198 Slur_score_parameters*,
199 Drul_array<Bound_info> extremes,
200 Drul_array<Offset> base_attachment);
204 get_detail (SCM alist, SCM sym)
206 SCM entry = scm_assq (sym, alist);
207 return robust_scm2double (ly_c_pair_p (entry)
214 init_score_param (Grob *me,
215 Slur_score_parameters *score_param)
217 SCM details = me->get_property ("slur-details");
219 score_param->region_size_
220 = (int) get_detail (details, ly_symbol2scm ("region-size"));
221 score_param->head_encompass_penalty_
222 = get_detail (details, ly_symbol2scm ("head-encompass-penalty"));
223 score_param->stem_encompass_penalty_
224 = get_detail (details, ly_symbol2scm ("stem-encompass-penalty"));
225 score_param->closeness_factor_
226 = get_detail (details, ly_symbol2scm ("closeness-factor"));
227 score_param->edge_attraction_factor_
228 = get_detail (details, ly_symbol2scm ("edge-attraction-factor"));
229 score_param->same_slope_penalty_
230 = get_detail (details, ly_symbol2scm ("same-slope-penalty"));
231 score_param->steeper_slope_factor_
232 = get_detail (details, ly_symbol2scm ("steeper-slope-factor"));
233 score_param->non_horizontal_penalty_
234 = get_detail (details, ly_symbol2scm ("non-horizontal-penalty"));
235 score_param->max_slope_
236 = get_detail (details, ly_symbol2scm ("max-slope"));
237 score_param->max_slope_factor_
238 = get_detail (details, ly_symbol2scm ("max-slope-factor"));
239 score_param->free_head_distance_
240 = get_detail (details, ly_symbol2scm ("free-head-distance"));
241 score_param->extra_object_collision_
242 = get_detail (details, ly_symbol2scm ("extra-object-collision"));
243 score_param->accidental_collision_
244 = get_detail (details, ly_symbol2scm ("accidental-collision"));
245 score_param->extra_encompass_free_distance_
246 = get_detail (details, ly_symbol2scm ("extra-encompass-free-distance"));
247 score_param->head_slur_distance_factor_
248 = get_detail (details, ly_symbol2scm ("head-slur-distance-factor"));
249 score_param->head_slur_distance_max_ratio_
250 = get_detail (details, ly_symbol2scm ("head-slur-distance-max-ratio"));
251 score_param->free_slur_distance_
252 = get_detail (details, ly_symbol2scm ("free-slur-distance"));
253 score_param->edge_slope_exponent_
254 = get_detail (details, ly_symbol2scm ("edge-slope-exponent"));
258 Slur_score_parameters::Slur_score_parameters (Grob *me)
260 init_score_param (me, this);
263 /* HDIR indicates which side (left or right) we are processing here. */
265 broken_trend_y (Grob *me, Grob **common, Direction hdir)
267 /* A broken slur should maintain the same vertical trend
268 the unbroken slur would have had. */
270 if (Spanner *mother = dynamic_cast<Spanner*> (me->original_))
272 int k = broken_spanner_index (dynamic_cast<Spanner*> (me));
274 if (j < 0 || j >= mother->broken_intos_.size ())
277 Grob *neighbor = mother->broken_intos_[j];
279 neighbor->set_property ("direction",
280 me->get_property ("direction"));
282 Spanner *common_mother
283 = dynamic_cast<Spanner*> (common[Y_AXIS]->original_);
285 = broken_spanner_index (dynamic_cast<Spanner*> (common[Y_AXIS]));
286 int common_j = common_k + hdir;
288 if (common_j < 0 || common_j >= common_mother->broken_intos_.size ())
291 Grob *common_next_system = common_mother->broken_intos_[common_j];
292 Link_array<Grob> neighbor_cols
293 = Pointer_group_interface__extract_grobs (neighbor, (Grob *)0,
297 = (hdir == RIGHT) ? neighbor_cols[0] : neighbor_cols.top ();
298 Grob *neighbor_common
299 = common_next_system->common_refpoint (neighbor_col, Y_AXIS);
301 Direction vdir = get_grob_direction (me);
303 = neighbor_col->extent (neighbor_common, Y_AXIS)
304 .linear_combination (int (vdir))
305 - common_next_system->relative_coordinate (neighbor_common, Y_AXIS);
307 Link_array<Grob> my_cols
308 = Pointer_group_interface__extract_grobs (me, (Grob *)0,
311 Grob *extreme_col = (hdir == RIGHT) ? my_cols.top () : my_cols[0];
312 Real y = extreme_col->extent (common[Y_AXIS], Y_AXIS)
313 .linear_combination (int ((my_cols.size () == 1) ? CENTER : vdir));
314 by = (y*neighbor_cols.size () + neighbor_y*my_cols.size ()) /
315 (my_cols.size () + neighbor_cols.size ());
321 get_encompass_info (Grob *me,
325 Grob *stem = unsmob_grob (col->get_property ("stem"));
327 Direction dir = get_grob_direction (me);
331 programming_error ("No stem for note column?");
332 ei.x_ = col->relative_coordinate (common[X_AXIS], X_AXIS);
333 ei.head_ = ei.stem_ = col->extent (common[Y_AXIS],
334 Y_AXIS)[get_grob_direction (me)];
337 Direction stem_dir = get_grob_direction (stem);
339 if (Grob *head = Note_column::first_head (col))
340 ei.x_ = head->extent (common[X_AXIS], X_AXIS).center ();
342 ei.x_ = col->extent (common[X_AXIS], X_AXIS).center ();
344 Grob *h = Stem::extremal_heads (stem)[Direction (dir)];
347 ei.head_ = ei.stem_ = col->extent (common[Y_AXIS], Y_AXIS)[dir];
351 ei.head_ = h->extent (common[Y_AXIS], Y_AXIS)[dir];
353 if ((stem_dir == dir)
354 && !stem->extent (stem, Y_AXIS).is_empty ())
356 ei.stem_ = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
357 if (Grob *b = Stem::get_beam (stem))
358 ei.stem_ += stem_dir * 0.5 * Beam::get_thickness (b);
360 Interval x = stem->extent (common[X_AXIS], X_AXIS);
361 ei.x_ = x.is_empty ()
362 ? stem->relative_coordinate (common[X_AXIS], X_AXIS)
373 get_default_dir (Grob*me)
375 Link_array<Grob> encompasses
376 = Pointer_group_interface__extract_grobs (me, (Grob*) 0, "note-columns");
379 for (int i= 0; i < encompasses.size (); i ++)
381 if (Note_column::dir (encompasses[i]) < 0)
392 MAKE_SCHEME_CALLBACK (Slur, after_line_breaking,1);
394 Slur::after_line_breaking (SCM smob)
396 Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
397 if (!scm_ilength (me->get_property ("note-columns")))
400 return SCM_UNSPECIFIED;
403 if (!get_grob_direction (me))
404 set_grob_direction (me, get_default_dir (me));
406 if (scm_ilength (me->get_property ("control-points")) < 4)
409 return SCM_UNSPECIFIED;
412 Drul_array<Bound_info>
413 get_bound_info (Spanner* me, Grob **common)
415 Drul_array<Bound_info> extremes;
418 Direction dir = get_grob_direction (me);
422 extremes[d].bound_ = me->get_bound (d);
423 if (Note_column::has_interface (extremes[d].bound_))
425 extremes[d].note_column_ = extremes[d].bound_;
426 extremes[d].stem_ = Note_column::get_stem (extremes[d].note_column_);
427 extremes[d].stem_dir_ = get_grob_direction (extremes[d].stem_);
428 extremes[d].stem_extent_[X_AXIS]
429 = extremes[d].stem_->extent (common[X_AXIS], X_AXIS);
430 extremes[d].stem_extent_[Y_AXIS]
431 = extremes[d].stem_->extent (common[Y_AXIS], Y_AXIS);
432 extremes[d].slur_head_
433 = Stem::extremal_heads (extremes[d].stem_)[dir];
434 if (!extremes[d].slur_head_
435 && Note_column::has_rests (extremes[d].bound_))
437 extremes[d].slur_head_ = Note_column::get_rest (extremes[d].bound_);
440 if (extremes[d].slur_head_)
441 extremes[d].slur_head_extent_
442 = extremes[d].slur_head_->extent (common[X_AXIS], X_AXIS);
444 extremes[d].staff_ = Staff_symbol_referencer
445 ::get_staff_symbol (extremes[d].stem_);
446 extremes[d].staff_space_ = Staff_symbol_referencer
447 ::staff_space (extremes[d].stem_);
451 right side anticipates on the next note.
453 extremes[d].neighbor_y_ = broken_trend_y (me, common, d);
455 while (flip (&d) != RIGHT);
460 set_end_points (Grob *me)
462 Link_array<Grob> columns
463 = Pointer_group_interface__extract_grobs (me, (Grob *) 0, "note-columns");
465 if (columns.is_empty ())
471 SCM eltlist = me->get_property ("note-columns");
472 SCM extra_list = me->get_property ("encompass-objects");
473 Spanner *sp = dynamic_cast<Spanner*> (me);
475 Grob *common[] = {0, 0};
476 for (int i = X_AXIS; i < NO_AXES; i++)
479 common[a] = common_refpoint_of_list (eltlist, me, a);
480 common[a] = common_refpoint_of_list (extra_list, common[a], a);
483 common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (RIGHT),
485 common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (LEFT),
488 Drul_array<Bound_info> extremes = get_bound_info (sp, common);
489 Drul_array<Offset> base_attachment
490 = get_base_attachments (sp, common, extremes);
491 Slur_score_parameters params (me);
492 Drul_array<Real> end_ys
493 = get_y_attachment_range (sp, common, ¶ms, extremes, base_attachment);
494 Array<Slur_score> scores = enumerate_attachments (me, common, ¶ms,
495 extremes, base_attachment,
498 generate_curves (me, common, ¶ms, extremes, base_attachment, &scores);
499 score_edges (me, common, ¶ms,extremes, base_attachment, &scores);
500 score_slopes (me, common, ¶ms,extremes, base_attachment, &scores);
501 score_encompass (me, common, ¶ms,extremes, base_attachment, &scores);
502 score_extra_encompass (me, common, ¶ms,extremes, base_attachment,
507 for (int i = 0; i < scores.size (); i++)
509 if (scores[i].score_ < opt)
511 opt = scores[i].score_;
516 #if DEBUG_SLUR_QUANTING
517 SCM inspect_quants = me->get_property ("inspect-quants");
518 if (to_boolean (me->get_paper ()
519 ->lookup_variable (ly_symbol2scm ("debug-slur-scoring")))
520 && ly_c_pair_p (inspect_quants))
522 Drul_array<Real> ins = ly_scm2interval (inspect_quants);
524 for (int i = 0; i < scores.size (); i ++)
526 Real d =fabs (scores[i].attachment_[LEFT][Y_AXIS] - ins[LEFT])
527 + fabs (scores[i].attachment_[RIGHT][Y_AXIS] - ins[RIGHT]);
535 programming_error ("Could not find quant.");
537 scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
540 me->set_property ("quant-score",
541 scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
544 Bezier b = scores[opt_idx].curve_;
545 SCM controls = SCM_EOL;
546 for (int i = 4; i--;)
548 Offset o = b.control_[i]
549 - Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
550 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
551 controls = scm_cons (ly_offset2scm (o), controls);
553 me->set_property ("control-points", controls);
557 TODO: should analyse encompasses to determine sensible region, and
558 should limit slopes available.
562 get_y_attachment_range (Spanner*me,
563 Grob **common, Slur_score_parameters *score_param,
564 Drul_array<Bound_info> extremes,
565 Drul_array<Offset> base_attachment)
567 Drul_array<Real> end_ys;
568 Direction dir = get_grob_direction (me);
572 if (extremes[d].note_column_)
575 * ((dir * (base_attachment[d][Y_AXIS] + score_param->region_size_* dir))
576 >? (dir * (dir + extremes[d].note_column_->extent (common[Y_AXIS],
578 >? (dir * base_attachment[-d][Y_AXIS]));
581 end_ys[d] = base_attachment[d][Y_AXIS] + score_param->region_size_ * dir;
583 while (flip (&d) != LEFT);
589 spanner_less (Spanner *s1, Spanner* s2)
595 b1[d] = s1->get_bound (d)->get_column ()->rank_;
596 b2[d] = s2->get_bound (d)->get_column ()->rank_;
597 } while (flip (&d) != LEFT);
599 return b2[LEFT] <= b1[LEFT] && b2[RIGHT] >= b1[RIGHT]
600 && (b2[LEFT] != b1[LEFT] || b2[RIGHT] != b1[RIGHT]);
605 get_base_attachments (Spanner *me,
606 Grob **common, Drul_array<Bound_info> extremes)
608 Link_array<Grob> columns
609 = Pointer_group_interface__extract_grobs (me, (Grob *)0, "note-columns");
610 Drul_array<Offset> base_attachment;
611 Real staff_space = Staff_symbol_referencer::staff_space ((Grob *) me);
612 Direction dir = get_grob_direction (me);
616 Grob *stem = extremes[d].stem_;
617 Grob *head = extremes[d].slur_head_;
620 if (!extremes[d].note_column_)
624 y = extremes[d].neighbor_y_;
625 x = extremes[d].bound_->extent (common[X_AXIS], X_AXIS)[d];
629 x = me->get_broken_left_end_align ();
630 if (extremes[RIGHT].bound_ == columns[0])
632 y = base_attachment[RIGHT][Y_AXIS];
636 y = columns[0]->extent (common[Y_AXIS], Y_AXIS)[dir];
643 (extremes[d].stem_ && extremes[-d].stem_
644 && Stem::get_beam (extremes[d].stem_) == Stem::get_beam (extremes[-d].stem_));
647 fixme: X coord should also be set in this case.
650 && extremes[d].stem_dir_ == dir
651 && Stem::get_beaming (stem, -d)
652 && (!spanner_less (me, Stem::get_beam (stem))
654 y = extremes[d].stem_extent_[Y_AXIS][dir];
656 y = head->extent (common[Y_AXIS], Y_AXIS)[dir];
657 y += dir * 0.5 * staff_space;
660 = (y - extremes[d].staff_->relative_coordinate (common[Y_AXIS],
662 * 2.0 / Staff_symbol::staff_space (extremes[d].staff_);
664 /* start off staffline. */
665 if (fabs (pos - my_round (pos)) < 0.2
666 && Staff_symbol_referencer::on_staffline (head, (int) rint (pos))
667 && Staff_symbol_referencer::line_count (head) - 1 >= rint (pos)
669 // TODO: calc from slur thick & line thick, parameter.
670 y += 1.5 * staff_space * dir / 10;
672 Grob * fh = Note_column::first_head (extremes[d].note_column_);
674 (fh ? fh->extent (common[X_AXIS], X_AXIS)
675 : extremes[d].bound_->extent (common[X_AXIS], X_AXIS))
676 .linear_combination (CENTER);
678 base_attachment[d] = Offset (x, y);
680 } while (flip (&d) != RIGHT);
682 return base_attachment;
686 generate_curves (Grob *me, Grob **common,
687 Slur_score_parameters *score_param,
688 Drul_array<Bound_info> extremes,
690 Array<Slur_score> *scores)
694 Real staff_space = Staff_symbol_referencer::staff_space ((Grob *) me);
695 Real r_0 = robust_scm2double (me->get_property ("ratio"), 0.33);
696 Real h_inf = staff_space *scm_to_double (me->get_property ("height-limit"));
697 for (int i = 0; i < scores->size (); i++)
699 Bezier bez = get_bezier (me,
703 (*scores)[i].attachment_, r_0, h_inf);
705 bez = avoid_staff_line (me, common, extremes, bez);
706 (*scores)[i].attachment_[LEFT] = bez.control_[0];
707 (*scores)[i].attachment_[RIGHT] = bez.control_[3];
708 (*scores)[i].curve_ = bez;
713 avoid_staff_line (Grob *me, Grob **common,
714 Drul_array<Bound_info> extremes,
718 Array<Real> ts = bez.solve_derivative (horiz);
719 Real lt = me->get_paper ()->get_dimension (ly_symbol2scm ("linethickness"));
720 Real thick = robust_scm2double (me->get_property ("thickness"), 1.0) * lt;
722 /* TODO: handle case of broken slur. */
724 && (extremes[LEFT].staff_ == extremes[RIGHT].staff_)
725 && extremes[LEFT].staff_ && extremes[RIGHT].staff_)
727 Real y = bez.curve_point (ts[0])[Y_AXIS];
729 Grob *staff = extremes[LEFT].staff_;
731 Real staff_space = extremes[LEFT].staff_space_;
732 Real p = 2 * (y - staff->relative_coordinate (common[Y_AXIS], Y_AXIS))
735 Real distance = fabs (my_round (p) - p); // in halfspaces
736 if (distance < 4 * thick
737 && (int) fabs (my_round (p))
738 <= 2 * Staff_symbol_referencer::staff_radius (staff) + 0.1
739 && (int (fabs (my_round (p))) % 2
740 != Staff_symbol_referencer::line_count (staff) % 2))
742 Direction resolution_dir =
743 (distance ? get_grob_direction (me) : Direction (sign (p - my_round (p))));
746 Real newp = my_round (p) + resolution_dir
749 Real dy = (newp - p) * staff_space / 2.0;
751 bez.control_[1][Y_AXIS] += dy;
752 bez.control_[2][Y_AXIS] += dy;
759 enumerate_attachments (Grob *me, Grob *common[],
760 Slur_score_parameters *score_param,
761 Drul_array<Bound_info> extremes,
762 Drul_array<Offset> base_attachment,
763 Drul_array<Real> end_ys)
767 Array<Slur_score> scores;
769 Direction dir = get_grob_direction (me);
770 Real staff_space = Staff_symbol_referencer::staff_space ((Grob *) me);
772 Drul_array<Offset> os;
773 os[LEFT] = base_attachment[LEFT];
774 Real minimum_length = staff_space
775 * robust_scm2double (me->get_property ("minimum-length"), 2.0);
777 for (int i = 0; dir * os[LEFT][Y_AXIS] <= dir * end_ys[LEFT]; i++)
779 os[RIGHT] = base_attachment[RIGHT];
780 for (int j = 0; dir * os[RIGHT][Y_AXIS] <= dir * end_ys[RIGHT]; j++)
784 Drul_array<bool> attach_to_stem (false, false);
787 os[d][X_AXIS] = base_attachment[d][X_AXIS];
788 if (extremes[d].stem_
789 && !Stem::is_invisible (extremes[d].stem_)
790 && extremes[d].stem_dir_ == dir)
792 Interval stem_y = extremes[d].stem_extent_[Y_AXIS];
793 stem_y.widen (0.25 * staff_space);
795 && stem_y.contains (os[d][Y_AXIS]))
797 os[d][X_AXIS] = extremes[d].slur_head_extent_[-d]
799 attach_to_stem[d] = true;
801 else if (dir *extremes[d].stem_extent_[Y_AXIS][dir]
802 < dir * os[d][Y_AXIS]
803 && !extremes[d].stem_extent_[X_AXIS].is_empty ()
806 os[d][X_AXIS] = extremes[d].stem_extent_[X_AXIS].center ();
809 while (flip (&d) != LEFT);
812 dz = os[RIGHT] - os[LEFT];
813 if (dz[X_AXIS] < minimum_length
814 || fabs (dz[Y_AXIS] / dz[X_AXIS]) > score_param->max_slope_
819 if (extremes[d].slur_head_)
821 os[d][X_AXIS] = extremes[d].slur_head_extent_.center ();
822 attach_to_stem[d] = false;
825 while (flip (&d) != LEFT);
828 dz = os[RIGHT] - os[LEFT];
831 if (extremes[d].slur_head_
832 && !attach_to_stem[d])
834 /* Horizontally move tilted slurs a little. Move
835 more for bigger tilts.
839 -= dir * extremes[d].slur_head_extent_.length ()
840 * sin (dz.arg ()) / 3;
843 while (flip (&d) != LEFT);
848 os[RIGHT][Y_AXIS] += dir * staff_space / 2;
851 os[LEFT][Y_AXIS] += dir * staff_space / 2;
854 assert (scores.size () > 0);
859 linear_interpolate (Real x, Real x1, Real x2, Real y1, Real y2)
861 return (x2 - x) / (x2 - x1) * y1 +
862 (x - x1) / (x2 - x1) * y2 ;
867 score_encompass (Grob *me, Grob *common[],
868 Slur_score_parameters *score_param,
869 Drul_array<Bound_info> extremes,
870 Drul_array<Offset> base_attach,
871 Array<Slur_score> *scores)
876 Link_array<Grob> encompasses
877 = Pointer_group_interface__extract_grobs (me, (Grob *)0, "note-columns");
878 Direction dir = get_grob_direction (me);
880 Array<Encompass_info> infos;
882 for (int i = 0; i < encompasses.size (); i++)
883 infos.push (get_encompass_info (me, encompasses[i], common));
885 for (int i = 0; i < scores->size (); i++)
887 Slur_score &configuration = scores->elem_ref (i);
888 Bezier const &bez (configuration.curve_);
892 Distances for heads that are between slur and line between
895 Array<Real> convex_head_distances;
896 Array<Real> edge_distances;
897 for (int j = 0; j < infos.size (); j++)
899 Real x = infos[j].x_;
902 bool r_edge = j==infos.size ()-1;
903 bool edge = l_edge || r_edge;
908 edge_distances.push (fabs (configuration.attachment_[l_edge ? LEFT : RIGHT][Y_AXIS]
909 - infos[j].get_point (dir)));
913 if (! (x < configuration.attachment_[RIGHT][X_AXIS]
914 && x > configuration.attachment_[LEFT][X_AXIS]))
917 Real y = bez.get_other_coordinate (X_AXIS, x);
920 Real head_dy = (y - infos[j].head_);
921 if (dir * head_dy < 0)
923 demerit += score_param->head_encompass_penalty_;
924 convex_head_distances.push (0.0);
929 ? (1 / fabs (head_dy) - 1 / score_param->free_head_distance_)
930 : score_param->head_encompass_penalty_;
931 hd = (hd >? 0)<? score_param->head_encompass_penalty_;
936 Real line_y = linear_interpolate (x,
937 configuration.attachment_[RIGHT][X_AXIS],
938 configuration.attachment_[LEFT][X_AXIS],
939 configuration.attachment_[RIGHT][Y_AXIS],
940 configuration.attachment_[LEFT][Y_AXIS]);
942 if ( 1 ) // dir * infos[j].get_point (dir) > dir *line_y )
946 dir * (dir * infos[j].get_point (dir)
949 Real d = fabs (closest - y);
951 convex_head_distances.push (d);
957 if (dir * (y - infos[j].stem_) < 0)
959 Real stem_dem =score_param->stem_encompass_penalty_ ;
960 if ((l_edge && dir == UP)
961 || (r_edge && dir == DOWN))
969 ext.add_point (infos[j].stem_);
970 ext.add_point (infos[j].head_);
973 demerit += -score_param->closeness_factor_
975 * (y - (ext[dir] + dir * score_param->free_head_distance_))
981 Real variance_penalty = 0.0;
983 if (convex_head_distances.size ())
985 Real avg_distance = 0.0;
986 Real min_dist = infinity_f;
987 for (int j = 0; j < convex_head_distances.size (); j++)
989 min_dist = min_dist <? convex_head_distances[j];
990 avg_distance += convex_head_distances[j];
994 For slurs over 3 or 4 heads, the average distance is not a
997 int n = convex_head_distances.size ();
998 if (convex_head_distances.size () <= 2)
1000 // Real min_edge_dist = 1e6;
1001 for (int j = 0; j < edge_distances.size (); j++)
1003 avg_distance += edge_distances[j];
1010 TODO: maybe it's better to use (avgdist - mindist)*factor
1014 variance_penalty = score_param->head_slur_distance_max_ratio_;
1016 variance_penalty = ((avg_distance / (min_dist +score_param->free_head_distance_)) - 1.0)
1017 <? variance_penalty;
1019 variance_penalty *= score_param->head_slur_distance_factor_;
1021 #if DEBUG_SLUR_QUANTING
1022 (*scores)[i].score_card_ += to_string ("C%.2f", demerit);
1023 (*scores)[i].score_card_ += to_string ("D%.2f", variance_penalty);
1026 (*scores)[i].score_ += demerit + variance_penalty;
1030 struct Extra_collision_info
1037 Extra_collision_info (Grob *g, Real idx, Interval x, Interval y, Real p)
1040 extents_[X_AXIS] = x;
1041 extents_[Y_AXIS] = y;
1045 Extra_collision_info ()
1054 score_extra_encompass (Grob *me, Grob *common[],
1055 Slur_score_parameters *score_param,
1056 Drul_array<Bound_info> extremes,
1057 Drul_array<Offset> base_attach,
1058 Array<Slur_score> *scores)
1063 Spanner *me_spanner = dynamic_cast<Spanner*> (me);
1065 Link_array<Grob> encompasses
1066 = Pointer_group_interface__extract_grobs (me, (Grob *)0,
1067 "encompass-objects");
1068 Direction dir = get_grob_direction (me);
1069 Real lt = me->get_paper ()->get_dimension (ly_symbol2scm ("linethickness"));
1070 Real thick = robust_scm2double (me->get_property ("thickness"), 1.0) * lt;
1072 Array<Extra_collision_info> collision_infos;
1073 for (int i = encompasses.size (); i--; )
1075 if (Slur::has_interface (encompasses[i]))
1077 Spanner * small_slur = dynamic_cast<Spanner*> (encompasses[i]);
1078 Bezier b = Slur::get_curve (small_slur);
1080 Offset relative (small_slur->relative_coordinate (common[X_AXIS], X_AXIS),
1081 small_slur->relative_coordinate (common[Y_AXIS], Y_AXIS));
1083 for (int k = 0; k < 3; k++)
1085 Direction hdir = Direction (k /2 - 1);
1088 Only take bound into account if small slur starts
1089 together with big slur.
1091 if (hdir && small_slur->get_bound (hdir) != me_spanner->get_bound (hdir))
1095 Offset z = b.curve_point ( k / 2.0);
1100 yext[dir] = z[Y_AXIS] + dir * thick * 1.0;
1102 Interval xext (-1, 1);
1103 xext = xext * (thick*2) + z[X_AXIS];
1104 Extra_collision_info info (small_slur,
1108 score_param->extra_object_collision_);
1109 collision_infos.push (info);
1114 Grob *g = encompasses [i];
1115 Interval xe = g->extent (common[X_AXIS], X_AXIS);
1116 Interval ye = g->extent (common[Y_AXIS], Y_AXIS);
1119 Real penalty = score_param->extra_object_collision_;
1120 if (Accidental_interface::has_interface (g))
1122 penalty = score_param->accidental_collision_;
1123 /* Begin copy accidental.cc */
1124 bool parens = false;
1125 if (to_boolean (g->get_property ("cautionary")))
1127 SCM cstyle = g->get_property ("cautionary-style");
1128 parens = ly_c_equal_p (cstyle, ly_symbol2scm ("parentheses"));
1131 SCM accs = g->get_property ("accidentals");
1132 SCM scm_style = g->get_property ("style");
1133 if (!scm_is_symbol (scm_style)
1135 && scm_ilength (accs) == 1)
1137 /* End copy accidental.cc */
1138 switch (scm_to_int (ly_car (accs)))
1154 ye.widen (thick * 0.5);
1155 xe.widen (thick * 1.0);
1156 Extra_collision_info info (g, xp, xe, ye, penalty);
1157 collision_infos.push (info);
1160 for (int i = 0; i < scores->size (); i++)
1163 for (int j = 0; j < collision_infos.size (); j++)
1165 Drul_array<Offset> attachment = scores->elem (i).attachment_;
1166 Interval slur_wid (attachment[LEFT][X_AXIS], attachment[RIGHT][X_AXIS]);
1169 to prevent numerical inaccuracies in
1170 Bezier::get_other_coordinate ().
1179 We need to check for the bound explicitly, since the
1180 slur-ending can be almost vertical, making the Y
1181 coordinate a bad approximation of the object-slur
1184 Item * as_item = dynamic_cast<Item*> (collision_infos[j].grob_);
1186 && as_item->get_column ()
1187 == extremes[d] .bound_->get_column ())
1188 || collision_infos[j].extents_[X_AXIS].contains (attachment[d][X_AXIS]))
1190 y = attachment[d][Y_AXIS];
1194 while (flip (&d) != LEFT);
1198 Real x = collision_infos[j].extents_[X_AXIS]
1199 .linear_combination (collision_infos[j].idx_);
1201 if (!slur_wid.contains (x))
1204 y = scores->elem (i).curve_.get_other_coordinate (X_AXIS, x);
1207 Real dist = collision_infos[j].extents_[Y_AXIS].distance (y);
1209 fabs (0 >? (score_param->extra_encompass_free_distance_ - dist)) /
1210 score_param->extra_encompass_free_distance_
1211 * collision_infos[j].penalty_;
1213 #if DEBUG_SLUR_QUANTING
1214 (*scores)[i].score_card_ += to_string ("X%.2f", demerit);
1216 (*scores)[i].score_ += demerit;
1221 TODO: should make edge penalties dependent on the direction that the
1222 slur-end is pointing.
1225 score_edges (Grob *me, Grob *common[],
1226 Slur_score_parameters * score_param,
1227 Drul_array<Bound_info> extremes,
1228 Drul_array<Offset> base_attach,
1229 Array<Slur_score> *scores)
1232 Direction dir = get_grob_direction (me);
1234 for (int i = 0; i < scores->size (); i++)
1237 Slur_score &config = scores->elem_ref (i);
1238 Offset dz = config.attachment_[RIGHT] - config.attachment_[LEFT];
1239 Real slope = dz[Y_AXIS] / dz[X_AXIS];
1242 Real y = config.attachment_[d][Y_AXIS];
1243 Real dy = fabs (y - base_attach[d][Y_AXIS]);
1245 Real factor = score_param->edge_attraction_factor_;
1246 Real demerit = factor * dy;
1247 if (extremes[d].stem_
1248 && extremes[d].stem_dir_ == dir
1249 && !Stem::get_beaming (extremes[d].stem_, -d)
1253 demerit *= exp (dir * d * slope
1254 * score_param->edge_slope_exponent_ );
1256 (*scores)[i].score_ += demerit;
1257 #if DEBUG_SLUR_QUANTING
1258 (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
1261 while (flip (&d) != LEFT);
1266 score_slopes (Grob *me, Grob *common[],
1267 Slur_score_parameters *score_param,
1268 Drul_array<Bound_info> extremes,
1269 Drul_array<Offset> base_attach,
1270 Array<Slur_score> * scores)
1275 Drul_array<Real> ys;
1279 if (extremes[d].slur_head_)
1280 ys[d] = extremes[d].slur_head_->relative_coordinate (common[Y_AXIS],
1283 ys[d] = extremes[d].neighbor_y_;
1285 while (flip (&d) != LEFT);
1288 = (extremes[LEFT].stem_ && Stem::get_beam (extremes[LEFT].stem_))
1289 || (extremes[RIGHT].stem_ && Stem::get_beam (extremes[RIGHT].stem_));
1291 Real dy = ys[RIGHT] - ys[LEFT];
1292 for (int i = 0; i < scores->size (); i++)
1294 Offset slur_dz = (*scores)[i].attachment_[RIGHT]
1295 - (*scores)[i].attachment_[LEFT];
1296 Real slur_dy = slur_dz[Y_AXIS];
1299 demerit += ((fabs (slur_dy / slur_dz[X_AXIS])
1300 - score_param->max_slope_) >? 0)
1301 * score_param->max_slope_factor_;
1303 /* 0.2: account for staffline offset. */
1304 Real max_dy = (fabs (dy) + 0.2);
1308 demerit += score_param->steeper_slope_factor_
1309 * ((fabs (slur_dy) -max_dy) >? 0);
1311 demerit += ((fabs (slur_dy/slur_dz[X_AXIS])
1312 - score_param->max_slope_) >? 0)
1313 * score_param->max_slope_factor_;
1316 && sign (slur_dy) != 0)
1317 demerit += score_param->non_horizontal_penalty_;
1321 && sign (slur_dy) != sign (dy))
1322 demerit += has_beams
1323 ? score_param->same_slope_penalty_ / 10
1324 : score_param->same_slope_penalty_;
1326 #if DEBUG_SLUR_QUANTING
1327 (*scores)[i].score_card_ += to_string ("S%.2f", d);
1329 (*scores)[i].score_ += demerit;
1338 fit_factor (Offset dz_unit, Offset dz_perp,
1339 Bezier curve, Direction d, Array<Offset> const &avoid)
1341 Real fit_factor = 0.0;
1342 Offset x0 = curve.control_[0];
1343 curve.translate (-x0);
1344 curve.rotate (-dz_unit.arg ());
1347 Interval curve_xext;
1348 curve_xext.add_point (curve.control_[0][X_AXIS]);
1349 curve_xext.add_point (curve.control_[3][X_AXIS]);
1351 for (int i = 0; i < avoid.size (); i++)
1353 Offset z = (avoid[i] - x0) ;
1354 Offset p (dot_product (z, dz_unit),
1355 d* dot_product (z, dz_perp));
1356 if (!curve_xext.contains (p[X_AXIS]))
1359 Real y = curve.get_other_coordinate (X_AXIS, p[X_AXIS]);
1362 fit_factor = fit_factor >? (p[Y_AXIS] / y);
1370 get_bezier (Grob *me,
1372 Slur_score_parameters *score_param,
1373 Drul_array<Bound_info> extremes,
1374 Drul_array<Offset> attachments,
1375 Real r_0, Real h_inf
1378 Link_array<Grob> encompasses
1379 = Pointer_group_interface__extract_grobs (me, (Grob *) 0, "note-columns");
1380 Direction dir = get_grob_direction (me);
1382 Array<Offset> avoid;
1383 for (int i = 0; i < encompasses.size (); i++)
1385 if (extremes[LEFT].note_column_ == encompasses[i]
1386 || extremes[RIGHT].note_column_ == encompasses[i])
1389 Encompass_info inf (get_encompass_info (me, encompasses[i], common));
1391 Real y = dir * ((dir * inf.head_) >? (dir *inf.stem_));
1393 avoid.push (Offset (inf.x_, y + dir * score_param->free_head_distance_));
1396 Link_array<Grob> extra_encompasses
1397 = Pointer_group_interface__extract_grobs (me, (Grob *)0, "encompass-objects");
1398 for (int i = 0; i < extra_encompasses.size (); i++)
1399 if (Slur::has_interface (extra_encompasses[i]))
1401 Grob * small_slur = extra_encompasses[i];
1402 Bezier b = Slur::get_curve (small_slur);
1404 Offset z = b.curve_point (0.5);
1405 z += Offset (small_slur->relative_coordinate (common[X_AXIS], X_AXIS),
1406 small_slur->relative_coordinate (common[Y_AXIS], Y_AXIS));
1408 z[Y_AXIS] += dir * score_param->free_slur_distance_;
1412 Offset dz = attachments[RIGHT]- attachments[LEFT];;
1413 Offset dz_unit = dz;
1414 dz_unit *= 1 / dz.length ();
1415 Offset dz_perp = dz_unit * Offset (0, 1);
1417 Real indent, height;
1418 get_slur_indent_height (&indent, &height, dz.length (), h_inf, r_0);
1420 Real excentricity = robust_scm2double (me->get_property ("excentricity"), 0);
1423 Real x1 = (excentricity + indent);
1424 Real x2 = (excentricity - indent);
1425 curve.control_[0] = attachments[LEFT];
1426 curve.control_[1] = attachments[LEFT] + dz_perp * height * dir + dz_unit * x1;
1427 curve.control_[2] = attachments[RIGHT] + dz_perp * height * dir
1429 curve.control_[3] = attachments[RIGHT];
1431 Real ff = fit_factor (dz_unit, dz_perp, curve, dir, avoid);
1432 Real len = dz.length ();
1436 len^2 > 4h^2 + 3 (i 1/3len)^2 - 1/3 len^2
1440 |bez' (0)| < | bez' (.5)|
1442 when (control2 - control1) has the same direction as
1443 (control3 - control0). */
1445 Real a1 = sqr (len) / 3.0;
1446 Real a2 = 0.75 * sqr (indent + len / 3.0);
1447 Real debug_h = sqrt (a1 - a2);
1450 max_h = sqrt (a1 - a2);
1453 programming_error ("FIXME: max_h is broken; setting to length / 3");
1456 height = height >? ((height * ff) <? max_h);
1458 curve.control_[0] = attachments[LEFT];
1459 curve.control_[1] = attachments[LEFT] + dz_perp * height * dir + dz_unit * x1;
1460 curve.control_[2] = attachments[RIGHT] + dz_perp * height * dir + dz_unit * x2;
1461 curve.control_[3] = attachments[RIGHT];