2 slur.cc -- implement score based Slur
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>
14 #include "font-interface.hh"
15 #include "text-item.hh"
16 #include "directional-element-interface.hh"
17 #include "group-interface.hh"
18 #include "lily-guile.hh"
20 #include "note-column.hh"
21 #include "output-def.hh"
23 #include "slur-bezier-bow.hh"
26 #include "staff-symbol-referencer.hh"
27 #include "staff-symbol.hh"
35 - avoid collision with staff line
36 - curve around flag/stem for x coordinate
41 struct Encompass_info {
62 Interval slur_head_extent_;
76 TODO: put in details list.
78 const int SLUR_REGION_SIZE = 5;
79 const Real HEAD_ENCOMPASS_PENALTY = 1000.0;
80 const Real STEM_ENCOMPASS_PENALTY = 30.0;
81 const Real CLOSENESS_FACTOR = 10;
82 const Real EDGE_ATTRACTION_FACTOR = 4;
83 const Real HEAD_FREE_SPACE = 0.3;
84 const Real SAME_SLOPE_PENALTY = 20;
85 const Real STEEPER_SLOPE_FACTOR = 50;
86 const Real NON_HORIZONTAL_PENALTY = 15;
87 const Real HEAD_STRICT_FREE_SPACE = 0.2;
88 const Real MAX_SLOPE = 1.1;
89 const Real MAX_SLOPE_FACTOR = 10;
92 #define DEBUG_SLUR_QUANTING 1
95 Drul_array<Offset> attachment_;
99 #if DEBUG_SLUR_QUANTING
111 static void add_column (Grob *me, Grob *col);
112 DECLARE_SCHEME_CALLBACK (print, (SCM));
113 static void score_slopes (Grob * me, Grob *common[],
114 Drul_array<Bound_info>,
115 Drul_array<Offset> base_attach,
116 Array<Slur_score> * scores);
118 static void score_encompass (Grob * me, Grob *common[],
119 Drul_array<Bound_info>,
120 Drul_array<Offset>, Array<Slur_score> * scores);
121 static void set_interface (Grob*);
122 static bool has_interface (Grob*);
123 static Array<Offset> get_encompass_offsets (Grob *me);
124 static Bezier get_curve (Grob *me);
125 static Bezier get_bezier (Grob *me, Drul_array<Offset>,Real,Real);
126 static Direction get_default_dir (Grob *me);
127 DECLARE_SCHEME_CALLBACK (after_line_breaking, (SCM));
128 DECLARE_SCHEME_CALLBACK (height, (SCM,SCM));
130 static void set_end_points (Grob*);
131 static Real get_boundary_notecolumn_y (Grob *me, Direction dir);
132 static Real broken_trend_y (Grob *me, Grob**, Direction dir);
133 static Offset get_attachment (Grob *me,Direction dir, Grob **common);
134 static void de_uglyfy (Grob *me,Slur_bezier_bow* bb, Real default_height);
135 static SCM set_extremities (Grob *me);
136 static void set_control_points (Grob *me);
137 static void check_slope (Grob *me);
138 static Encompass_info get_encompass_info (Grob *me, Grob *col, Grob **common);
142 New_slur::broken_trend_y (Grob *me, Grob**common, Direction hdir)
145 A broken slur should maintain the same vertical trend
146 the unbroken slur would have had.
149 if (Spanner *mother = dynamic_cast<Spanner*> (me->original_))
151 int k = broken_spanner_index (dynamic_cast<Spanner*> (me));
153 if (j < 0 || j >= mother->broken_intos_.size ())
156 Grob *neighbor = mother->broken_intos_[j];
158 neighbor->set_property ("direction",
159 me->get_property ("direction"));
161 Spanner * common_mother = dynamic_cast<Spanner*> (common[Y_AXIS]->original_);
162 int common_k = broken_spanner_index (dynamic_cast<Spanner*> (common[Y_AXIS]));
163 int common_j = common_k + hdir;
165 if (common_j < 0 || common_j >= common_mother->broken_intos_.size())
169 Grob *common_next_system = common_mother->broken_intos_[common_j];
170 Link_array<Grob> neighbor_cols =
171 Pointer_group_interface__extract_grobs (neighbor, (Grob*)0, "note-columns");
173 Grob * neighbor_col = (hdir == RIGHT) ? neighbor_cols[0] : neighbor_cols.top ();
174 Grob * neighbor_common = common_next_system->common_refpoint (neighbor_col, Y_AXIS);
176 Direction vdir = get_grob_direction (me);
178 neighbor_col->extent (neighbor_common, Y_AXIS)
179 .linear_combination (int(neighbor_cols.size()==1 ? CENTER : vdir))
180 - common_next_system->relative_coordinate (neighbor_common, Y_AXIS);
182 Link_array<Grob> my_cols =
183 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
185 Grob *extreme_col = (hdir == RIGHT) ? my_cols.top() : my_cols[0];
186 Real y = extreme_col->extent (common[Y_AXIS], Y_AXIS).linear_combination (vdir);
188 by = (y*neighbor_cols.size() + neighbor_y*my_cols.size()) /
189 (my_cols.size() + neighbor_cols.size());
195 New_slur::set_interface (Grob*me)
197 /* Copy to mutable list. */
198 me->set_property ("attachment",
199 ly_deep_copy (me->get_property ("attachment")));
203 New_slur::add_column (Grob*me, Grob*n)
205 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
206 me->add_dependency (n);
208 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (n));
212 New_slur::get_encompass_info (Grob *me,
216 Grob* stem = unsmob_grob (col->get_property ("stem"));
220 Direction dir = get_grob_direction (me);
224 programming_error ("No stem for note column?");
225 ei.x_ = col->relative_coordinate (common[X_AXIS], X_AXIS);
226 ei.head_ = ei.stem_ = col->extent (common[Y_AXIS], Y_AXIS)[get_grob_direction (me)];
229 Direction stem_dir = get_grob_direction (stem);
231 if (Grob *head = Note_column::first_head (col))
232 ei.x_ = head->extent (common[X_AXIS], X_AXIS).center ();
234 ei.x_ = col->extent (common[X_AXIS], X_AXIS).center ();
236 Grob * h = Stem::extremal_heads (stem)[Direction (dir)];
239 ei.head_ = ei.stem_ = col->extent (common[Y_AXIS], Y_AXIS)[dir];
243 ei.head_ = h->extent (common[Y_AXIS], Y_AXIS)[dir];
245 if ((stem_dir == dir)
246 && !stem->extent (stem, Y_AXIS).is_empty ())
248 ei.stem_ = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
258 New_slur::get_default_dir (Grob*me)
260 Link_array<Grob> encompasses =
261 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
264 for (int i=0; i < encompasses.size (); i ++)
266 if (Note_column::dir (encompasses[i]) < 0)
275 MAKE_SCHEME_CALLBACK (New_slur, after_line_breaking,1);
277 New_slur::after_line_breaking (SCM smob)
279 Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
280 if (!scm_ilength (me->get_property ("note-columns")))
283 return SCM_UNSPECIFIED;
286 if (!get_grob_direction (me))
287 set_grob_direction (me, get_default_dir (me));
292 return SCM_UNSPECIFIED;
296 New_slur::get_bezier (Grob *me, Drul_array<Offset> extremes,
300 Array<Offset> encompasses;
301 encompasses.push (extremes[LEFT]);
302 encompasses.push (extremes[RIGHT]);
304 Slur_bezier_bow bb (encompasses,
305 get_grob_direction (me), h_inf, r_0);
307 return bb.get_bezier ();
311 New_slur::set_end_points (Grob *me)
313 Link_array<Grob> columns =
314 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
316 if (columns.is_empty ())
321 Real staff_space = Staff_symbol_referencer::staff_space ((Grob*)me);
322 Real minimum_length = staff_space * robust_scm2double (me->get_property ("minimum-length"),
325 Drul_array<Offset> base_attachment;
327 SCM eltlist = me->get_property ("note-columns");
328 Grob *common[] = {common_refpoint_of_list (eltlist, me, X_AXIS),
329 common_refpoint_of_list (eltlist, me, Y_AXIS)};
332 Spanner* sp = dynamic_cast<Spanner*> (me);
333 common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
334 common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (LEFT), X_AXIS);
337 Drul_array<Bound_info> extremes;
338 Direction dir = get_grob_direction (me);
343 extremes[d].bound_ = dynamic_cast<Spanner*> (me)->get_bound (d);
345 if (Note_column::has_interface (extremes[d].bound_))
347 extremes[d].note_column_ = extremes[d].bound_;
348 extremes[d].stem_ = Note_column::get_stem (extremes[d].note_column_);
349 extremes[d].stem_dir_ = get_grob_direction (extremes[d].stem_);
350 extremes[d].stem_extent_[X_AXIS] = extremes[d].stem_->extent (common[X_AXIS], X_AXIS);
351 extremes[d].stem_extent_[Y_AXIS] = extremes[d].stem_->extent (common[Y_AXIS], Y_AXIS);
352 extremes[d].slur_head_ = Stem::extremal_heads (extremes[d].stem_)[dir];
353 extremes[d].slur_head_extent_ = extremes[d].slur_head_->extent (common[X_AXIS], X_AXIS);
354 extremes[d].staff_ = Staff_symbol_referencer::get_staff_symbol (extremes[d].slur_head_);
358 extremes[d].neighbor_y_ = broken_trend_y (me, common, d);
360 } while (flip (&d) != LEFT);
364 Grob *stem = extremes[d].stem_;
365 Grob *head = extremes[d].slur_head_;
368 if (!extremes[d].note_column_)
370 y = extremes[d].neighbor_y_;
372 x = extremes[d].bound_->extent (common[X_AXIS], X_AXIS)[d];
374 x = sp->get_broken_left_end_align ();
379 && extremes[d].stem_dir_ == dir
380 && Stem::get_beaming (stem, -d)
381 && columns.size () > 2
384 y = extremes[d].stem_extent_[Y_AXIS][dir];
388 y = head->extent (common[Y_AXIS], Y_AXIS)[dir];
390 y += dir * 0.5 * staff_space;
392 Real pos = 2.0 * (y - extremes[d].staff_->relative_coordinate (common[Y_AXIS], Y_AXIS))
393 / Staff_symbol::staff_space (extremes[d].staff_);
398 if (fabs (pos - round (pos)) < 0.2
399 && Staff_symbol_referencer::on_staffline (head, (int) rint (pos))
400 && Staff_symbol_referencer::line_count (head) -1 >= rint (pos)
402 y += staff_space * dir / 10 ;
404 Grob * fh = Note_column::first_head (extremes[d].note_column_);
405 x = fh->extent (common[X_AXIS], X_AXIS).linear_combination (CENTER);
407 base_attachment[d] = Offset (x, y);
409 } while (flip (&d) != LEFT);
414 if (extremes[d].note_column_)
416 end_ys[d] = dir * ((dir * (base_attachment[d][Y_AXIS] + 4.0 *dir))
417 >? (dir * (dir + extremes[d].note_column_->extent(common[Y_AXIS],Y_AXIS)[dir]))
418 >? (dir * base_attachment[-d][Y_AXIS])
423 end_ys[d] = extremes[d].neighbor_y_ + 4.0 * dir ;
425 } while (flip (&d) != LEFT);
427 Array<Slur_score> scores;
429 Drul_array<Offset> os;
432 os[LEFT] = base_attachment[LEFT];
434 for (int i = 0; dir * os[LEFT][Y_AXIS] <= dir * end_ys[LEFT]; i++)
436 os[RIGHT] = base_attachment[RIGHT];
437 for (int j = 0; dir *os[RIGHT][Y_AXIS] <= dir * end_ys[RIGHT]; j++)
444 os[d][X_AXIS] = base_attachment[d][X_AXIS];
445 if (extremes[d].stem_
446 && !Stem::is_invisible (extremes[d].stem_)
447 && extremes[d].stem_dir_ == dir
450 if (extremes[d].stem_extent_[Y_AXIS].contains (os[d][Y_AXIS]))
452 os[d][X_AXIS] -= d * extremes[d].slur_head_extent_.length ();
454 else if (dir *extremes[d].stem_extent_[Y_AXIS][dir] < dir * os[d][Y_AXIS])
456 os[d][X_AXIS] = extremes[d].stem_extent_[X_AXIS].center();
459 } while (flip (&d) != LEFT);
461 Offset dz = os[RIGHT] - os[LEFT];
462 if (dz[X_AXIS] < minimum_length
463 || fabs (dz[Y_AXIS] / dz[X_AXIS]) > MAX_SLOPE
467 if (extremes[d].slur_head_)
468 os[d][X_AXIS] = extremes[d].slur_head_extent_.center ();
469 } while (flip (&d) != LEFT);
475 os[RIGHT][Y_AXIS] += dir * staff_space / 2;
478 os[LEFT][Y_AXIS] += dir * staff_space /2 ;
482 Real r_0 = robust_scm2double (me->get_property ("ratio"), 1);
483 Real h_inf = staff_space * ly_scm2double (me->get_property ("height-limit"));
484 for (int i = scores.size(); i-- ;)
486 scores[i].curve_ = get_bezier (me, scores[i].attachment_,
491 score_encompass (me, common, extremes, base_attachment, &scores);
492 score_slopes (me, common, extremes, base_attachment, &scores);
496 for (int i = scores.size (); i--;)
498 if (scores[i].score_ < opt)
500 opt = scores[i].score_;
505 Bezier const &b = scores[opt_idx].curve_;
507 SCM controls = SCM_EOL;
508 for (int i = 4; i--;)
510 Offset o = b.control_[i] -
511 Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
512 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
514 controls = scm_cons (ly_offset2scm (o), controls);
517 me->set_property ("control-points", controls);
519 #if DEBUG_SLUR_QUANTING
520 scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
523 me->set_property ("quant-score",
524 scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
530 New_slur::score_encompass (Grob * me, Grob *common[],
531 Drul_array<Bound_info> extremes,
532 Drul_array<Offset> base_attach,
533 Array<Slur_score> * scores)
535 Link_array<Grob> encompasses =
536 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
537 Direction dir = get_grob_direction (me);
539 Array<Encompass_info> infos;
541 for (int i = 0; i < encompasses.size(); i++)
542 infos.push (get_encompass_info (me, encompasses[i], common));
544 for (int i =0 ; i < scores->size (); i++)
546 Bezier const &bez (scores->elem (i).curve_);
548 for (int j = 0; j < infos.size(); j++)
550 Real x = infos[j].x_;
552 if (!(x < scores->elem (i).attachment_[RIGHT][X_AXIS]
553 &&x > scores->elem (i).attachment_[LEFT][X_AXIS]))
556 Real y = bez.get_other_coordinate (X_AXIS, x);
558 if (dir * (y - infos[j].head_) < 0)
559 demerit += HEAD_ENCOMPASS_PENALTY;
561 if (dir * (y - infos[j].stem_) < 0)
562 demerit += STEM_ENCOMPASS_PENALTY;
563 else if (j && j < encompasses.size () - 1)
566 ext.add_point (infos[j].stem_);
567 ext.add_point (infos[j].head_);
569 demerit += - CLOSENESS_FACTOR * (dir * (y - (ext[dir] + dir * HEAD_FREE_SPACE)) <? 0) /
577 EDGE_ATTRACTION_FACTOR
578 * fabs (scores->elem (i).attachment_[d][Y_AXIS] - base_attach[d][Y_AXIS]);
579 if (extremes[d].stem_ && extremes[d].stem_dir_ == dir)
583 } while (flip (&d) != LEFT);
585 #if DEBUG_SLUR_QUANTING
586 (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
589 (*scores)[i].score_ += demerit;
595 New_slur::score_slopes (Grob * me, Grob *common[],
596 Drul_array<Bound_info> extremes,
597 Drul_array<Offset> base_attach,
598 Array<Slur_score> * scores)
600 Direction dir = get_grob_direction (me);
606 if (extremes[d].slur_head_)
607 ys[d] = extremes[d].slur_head_ ->relative_coordinate (common[Y_AXIS], Y_AXIS);
609 ys[d] = extremes[d].neighbor_y_;
610 } while (flip (&d) != LEFT);
613 (extremes[LEFT].stem_ && Stem::get_beam (extremes[LEFT].stem_))
614 || (extremes[RIGHT].stem_ && Stem::get_beam (extremes[RIGHT].stem_));
616 Real dy = ys[RIGHT] - ys[LEFT];
617 for (int i =0 ; i < scores->size (); i++)
619 Offset slur_dz = (*scores)[i].attachment_[RIGHT]
620 - (*scores)[i].attachment_[LEFT];
622 Real slur_dy = slur_dz[Y_AXIS];
628 demerit += STEEPER_SLOPE_FACTOR * (dir * (fabs (slur_dy) - fabs (dy)) >? 0);
630 demerit += ((fabs (slur_dy/slur_dz[X_AXIS]) - MAX_SLOPE)>?0) * MAX_SLOPE_FACTOR;
632 if (sign (dy) == 0 &&
634 demerit += NON_HORIZONTAL_PENALTY;
638 && sign (slur_dy) != sign (dy))
640 has_beams ? SAME_SLOPE_PENALTY/10 : SAME_SLOPE_PENALTY;
642 #if DEBUG_SLUR_QUANTING
643 (*scores)[i].score_card_ += to_string ("S%.2f",d);
645 (*scores)[i].score_ += demerit;
651 New_slur::get_curve (Grob*me)
655 for (SCM s= me->get_property ("control-points"); s != SCM_EOL; s = ly_cdr (s))
657 b.control_[i++] = ly_scm2offset (ly_car (s));
664 MAKE_SCHEME_CALLBACK (New_slur, height, 2);
666 New_slur::height (SCM smob, SCM ax)
668 Axis a = (Axis)ly_scm2int (ax);
669 Grob * me = unsmob_grob (smob);
670 assert (a == Y_AXIS);
672 SCM mol = me->get_uncached_stencil ();
674 if (Stencil * m = unsmob_stencil (mol))
676 return ly_interval2scm (ext);
680 Ugh should have dash-length + dash-period
682 MAKE_SCHEME_CALLBACK (New_slur, print,1);
684 New_slur::print (SCM smob)
686 Grob * me = unsmob_grob (smob);
687 if (!scm_ilength (me->get_property ("note-columns")))
693 Real base_thick = robust_scm2double (me->get_property ("thickness"), 1);
694 Real thick = base_thick * Staff_symbol_referencer::line_thickness (me);
696 Real ss = Staff_symbol_referencer::staff_space (me);
697 Bezier one = get_curve (me);
699 // get_curve may suicide
700 if (!scm_ilength (me->get_property ("note-columns")))
706 TODO: replace dashed with generic property.
708 SCM d = me->get_property ("dashed");
709 if (ly_c_number_p (d))
710 a = Lookup::dashed_slur (one, thick, thick * robust_scm2double (d, 0));
712 a = Lookup::slur (one, get_grob_direction (me) * base_thick * ss / 10.0,
715 #if DEBUG_SLUR_QUANTING
716 SCM quant_score = me->get_property ("quant-score");
718 if (debug_beam_quanting_flag &&
719 ly_c_string_p (quant_score))
722 SCM properties = Font_interface::text_font_alist_chain (me);
724 Stencil tm = *unsmob_stencil (Text_item::interpret_markup
725 (me->get_paper ()->self_scm (), properties, quant_score));
726 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0, 0);
730 return a.smobbed_copy ();
737 ADD_INTERFACE (New_slur, "new-slur-interface",
739 "attachment attachment-offset beautiful control-points dashed details de-uglify-parameters direction extremity-function extremity-offset-alist height-limit note-columns ratio slope-limit thickness y-free");