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>
13 #include "font-interface.hh"
14 #include "text-item.hh"
15 #include "directional-element-interface.hh"
16 #include "group-interface.hh"
17 #include "lily-guile.hh"
19 #include "note-column.hh"
20 #include "output-def.hh"
22 #include "slur-bezier-bow.hh"
25 #include "staff-symbol-referencer.hh"
26 #include "staff-symbol.hh"
34 - avoid collision with staff line
35 - curve around flag/stem for x coordinate
40 struct Encompass_info {
53 TODO: put in details list.
55 const int SLUR_REGION_SIZE = 5;
56 const Real HEAD_ENCOMPASS_PENALTY = 1000.0;
57 const Real STEM_ENCOMPASS_PENALTY = 30.0;
58 const Real CLOSENESS_FACTOR = 10;
59 const Real EDGE_ATTRACTION_FACTOR = 4;
60 const Real HEAD_FREE_SPACE = 0.3;
61 const Real SAME_SLOPE_PENALTY = 20;
62 const Real STEEPER_SLOPE_FACTOR = 50;
63 const Real NON_HORIZONTAL_PENALTY = 15;
64 const Real HEAD_STRICT_FREE_SPACE = 0.2;
65 const Real MAX_SLOPE = 1.4;
66 const Real MAX_SLOPE_FACTOR = 10;
69 #define DEBUG_SLUR_QUANTING 1
72 Drul_array<Offset> attachment_;
76 #if DEBUG_SLUR_QUANTING
88 static void add_column (Grob *me, Grob *col);
89 DECLARE_SCHEME_CALLBACK (print, (SCM));
90 static void score_slopes (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
91 Array<Slur_score> * scores);
93 static void score_encompass (Grob * me, Grob *common[],
94 Drul_array<Offset>, Array<Slur_score> * scores);
95 static void set_interface (Grob*);
96 static bool has_interface (Grob*);
97 static Array<Offset> get_encompass_offsets (Grob *me);
98 static Bezier get_curve (Grob *me);
99 static Bezier get_bezier (Grob *me, Drul_array<Offset>,Real,Real);
100 static Direction get_default_dir (Grob *me);
101 DECLARE_SCHEME_CALLBACK (after_line_breaking, (SCM));
102 DECLARE_SCHEME_CALLBACK (height, (SCM,SCM));
104 static void set_end_points (Grob*);
105 static Real get_boundary_notecolumn_y (Grob *me, Direction dir);
106 static Offset broken_trend_offset (Grob *me, Direction dir);
107 static Offset get_attachment (Grob *me,Direction dir, Grob **common);
108 static void de_uglyfy (Grob *me,Slur_bezier_bow* bb, Real default_height);
109 static SCM set_extremities (Grob *me);
110 static void set_control_points (Grob *me);
111 static void check_slope (Grob *me);
112 static Encompass_info get_encompass_info (Grob *me, Grob *col, Grob **common);
116 New_slur::set_interface (Grob*me)
118 /* Copy to mutable list. */
119 me->set_property ("attachment",
120 ly_deep_copy (me->get_property ("attachment")));
124 New_slur::add_column (Grob*me, Grob*n)
126 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
127 me->add_dependency (n);
129 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (n));
133 New_slur::get_encompass_info (Grob *me,
137 Grob* stem = unsmob_grob (col->get_property ("stem"));
141 Direction dir = get_grob_direction (me);
145 programming_error ("No stem for note column?");
146 ei.x_ = col->relative_coordinate (common[X_AXIS], X_AXIS);
147 ei.head_ = ei.stem_ = col->relative_coordinate (common[Y_AXIS], Y_AXIS);
150 Direction stem_dir = get_grob_direction (stem);
152 if (Grob *head = Note_column::first_head (col))
153 ei.x_ = head->extent (common[X_AXIS], X_AXIS).center ();
155 ei.x_ = col->extent (common[X_AXIS], X_AXIS).center ();
157 Grob * h = Stem::extremal_heads (stem)[Direction (dir)];
160 ei.head_ = ei.stem_ = col->relative_coordinate (common[Y_AXIS], Y_AXIS);
164 ei.head_ = h->extent (common[Y_AXIS], Y_AXIS)[dir];
166 if ((stem_dir == dir)
167 && !stem->extent (stem, Y_AXIS).is_empty ())
169 ei.stem_ = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
179 New_slur::get_default_dir (Grob*me)
181 Link_array<Grob> encompasses =
182 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
185 for (int i=0; i < encompasses.size (); i ++)
187 if (Note_column::dir (encompasses[i]) < 0)
196 MAKE_SCHEME_CALLBACK (New_slur, after_line_breaking,1);
198 New_slur::after_line_breaking (SCM smob)
200 Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
201 if (!scm_ilength (me->get_property ("note-columns")))
204 return SCM_UNSPECIFIED;
207 if (!get_grob_direction (me))
208 set_grob_direction (me, get_default_dir (me));
211 if (!Note_column::has_interface (me->get_bound (LEFT))
212 || !Note_column::has_interface (me->get_bound (RIGHT)))
213 me->suicide (); // fixme.
217 return SCM_UNSPECIFIED;
221 New_slur::get_bezier (Grob *me, Drul_array<Offset> extremes,
225 Array<Offset> encompasses;
226 encompasses.push (extremes[LEFT]);
227 encompasses.push (extremes[RIGHT]);
229 Slur_bezier_bow bb (encompasses,
230 get_grob_direction (me), h_inf, r_0);
232 return bb.get_bezier ();
236 New_slur::set_end_points (Grob *me)
238 Link_array<Grob> columns =
239 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
241 if (columns.is_empty ())
246 Real staff_space = Staff_symbol_referencer::staff_space ((Grob*)me);
247 Drul_array<Grob *> extremes (columns[0], columns.top ());
248 Direction dir = get_grob_direction (me);
250 Drul_array<Offset> base_attachment;
252 SCM eltlist = me->get_property ("note-columns");
253 Grob *common[] = {common_refpoint_of_list (eltlist, me, X_AXIS),
254 common_refpoint_of_list (eltlist, me, Y_AXIS)};
257 Spanner* sp = dynamic_cast<Spanner*> (me);
258 common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
259 common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (LEFT), X_AXIS);
262 Drul_array<Grob*> staves;
265 Grob *stem = Note_column::get_stem (extremes[d]);
266 Grob * h = Stem::extremal_heads (stem)[Direction (dir)];
267 staves[d] = Staff_symbol_referencer::get_staff_symbol (h);
269 common[Y_AXIS] = common[Y_AXIS]->common_refpoint (staves[d], Y_AXIS);
270 } while (flip (&d) != LEFT);
275 Grob *stem = Note_column::get_stem (extremes[d]);
276 Grob * h = Stem::extremal_heads (stem)[dir];
281 && get_grob_direction (stem) == dir
282 && Stem::get_beam (stem))
284 y = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
288 y = h->extent (common[Y_AXIS], Y_AXIS)[dir];
291 y += dir * 0.5 * staff_space;
293 Grob * staff = Staff_symbol_referencer::get_staff_symbol (h);
294 Real pos = 2.0 * (y - staff->relative_coordinate (common[Y_AXIS], Y_AXIS)) / Staff_symbol::staff_space (staff);
300 if (fabs (pos - round (pos)) < 0.2
301 && Staff_symbol_referencer::on_staffline (h, (int) rint (pos)))
302 y += staff_space * dir / 10 ;
306 Grob * fh = Note_column::first_head (extremes[d]);
307 Real x = fh->extent (common[X_AXIS], X_AXIS).linear_combination (CENTER);
309 if (get_grob_direction (stem) == dir
312 x -= d * fh->extent(fh, X_AXIS).length ();
315 base_attachment[d] = Offset (x, y);
317 } while (flip (&d) != LEFT);
319 Drul_array<Real> staff_offsets;
323 staff_offsets[d] = staves[d]->relative_coordinate (common[Y_AXIS], Y_AXIS);
324 end_ys[d] = dir * ((dir * (base_attachment[d][Y_AXIS] + 4.0 *dir)) >?
325 (dir * (dir + extremes[d]->extent(common[Y_AXIS],Y_AXIS)[dir])));
326 } while (flip (&d) != LEFT);
328 Array<Slur_score> scores;
330 Drul_array<Offset> os;
333 os[LEFT] = base_attachment[LEFT];
335 for (int i = 0; dir * os[LEFT][Y_AXIS] < dir * end_ys[LEFT]; i++)
337 os[RIGHT] = base_attachment[RIGHT];
338 for (int j = 0; dir *os[RIGHT][Y_AXIS] < dir * end_ys[RIGHT]; j++)
345 Real incr = dir * staff_space;
346 if (Staff_symbol_referencer::staff_radius (staves[RIGHT])
347 < fabs ((os[RIGHT][Y_AXIS] - staff_offsets[RIGHT]) / staff_space))
350 os[RIGHT][Y_AXIS] += incr;
353 Real incr = dir * staff_space;
354 if (Staff_symbol_referencer::staff_radius (staves[LEFT])
355 < fabs ((os[LEFT][Y_AXIS] - staff_offsets[LEFT]) / staff_space))
358 os[LEFT][Y_AXIS] += incr;
362 Real r_0 = robust_scm2double (me->get_property ("ratio"), 1);
363 Real h_inf = staff_space * ly_scm2double (me->get_property ("height-limit"));
364 for (int i = scores.size(); i-- ;)
366 scores[i].curve_ = get_bezier (me, scores[i].attachment_,
371 score_encompass (me, common, base_attachment, &scores);
372 score_slopes (me, common, base_attachment, &scores);
376 for (int i = scores.size (); i--;)
378 if (scores[i].score_ < opt)
380 opt = scores[i].score_;
385 Bezier const &b = scores[opt_idx].curve_;
387 SCM controls = SCM_EOL;
388 for (int i = 4; i--;)
390 Offset o = b.control_[i] -
391 Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
392 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
394 controls = scm_cons (ly_offset2scm (o), controls);
397 me->set_property ("control-points", controls);
399 #if DEBUG_SLUR_QUANTING
400 scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
403 me->set_property ("quant-score",
404 scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
410 New_slur::score_encompass (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
411 Array<Slur_score> * scores)
413 Link_array<Grob> encompasses =
414 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
415 Direction dir = get_grob_direction (me);
417 Array<Encompass_info> infos;
418 Drul_array<Grob *> extremes (encompasses[0], encompasses.top ());
421 int last = encompasses.size () - 2;
423 for (int i = first; i <= last; i++)
424 infos.push (get_encompass_info (me, encompasses[i], common));
426 Drul_array<Grob*> stems;
429 Grob *stem = Note_column::get_stem (extremes [d]);
431 } while (flip (&d) != LEFT);
435 for (int i =0 ; i < scores->size (); i++)
437 Bezier const &bez (scores->elem (i).curve_);
439 for (int j = 0; j < infos.size(); j++)
441 Real x = infos[j].x_;
443 if (!(x < scores->elem (i).attachment_[RIGHT][X_AXIS]
444 &&x > scores->elem (i).attachment_[LEFT][X_AXIS]))
447 Real y = bez.get_other_coordinate (X_AXIS, x);
449 if (dir * (y - infos[j].head_) < 0)
450 demerit += HEAD_ENCOMPASS_PENALTY;
452 if (dir * (y - infos[j].stem_) < 0)
453 demerit += STEM_ENCOMPASS_PENALTY;
457 ext.add_point (infos[j].stem_);
458 ext.add_point (infos[j].head_);
460 demerit += - CLOSENESS_FACTOR * (dir * (y - (ext[dir] + dir * HEAD_FREE_SPACE)) <? 0) /
469 EDGE_ATTRACTION_FACTOR
470 * fabs (scores->elem (i).attachment_[d][Y_AXIS] - base_attach[d][Y_AXIS]);
471 if (get_grob_direction (stems[d]) == dir)
475 } while (flip (&d) != LEFT);
477 #if DEBUG_SLUR_QUANTING
478 (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
481 (*scores)[i].score_ += demerit;
487 New_slur::score_slopes (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
488 Array<Slur_score> * scores)
490 Link_array<Grob> columns =
491 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
493 Drul_array<Grob *> extremes (columns[0], columns.top ());
494 Direction dir = get_grob_direction (me);
498 Drul_array<Direction> stem_dirs;
499 Drul_array<bool> beams;
501 Grob *stem = Note_column::get_stem (extremes [d]);
502 ys[d] = Stem::extremal_heads (stem)[Direction (dir)]
503 ->relative_coordinate (common[Y_AXIS], Y_AXIS);
505 stem_dirs[d] = get_grob_direction (stem);
506 beams[d] = Stem::get_beam (stem);
507 } while (flip (&d) != LEFT);
509 Real dx = extremes[RIGHT]->relative_coordinate (common[X_AXIS],X_AXIS)
510 - extremes[LEFT]->relative_coordinate (common[X_AXIS],X_AXIS);
512 Real dy = ys[RIGHT] - ys[LEFT];
513 for (int i =0 ; i < scores->size (); i++)
515 Real slur_dy = (*scores)[i].attachment_[RIGHT][Y_AXIS]
516 - (*scores)[i].attachment_[LEFT][Y_AXIS];
521 if(! (beams[LEFT] || beams[RIGHT]))
522 demerit += STEEPER_SLOPE_FACTOR * (dir * (fabs (slur_dy) - fabs (dy)) >? 0);
524 demerit += ((fabs (slur_dy/dx) - MAX_SLOPE)>?0) * MAX_SLOPE_FACTOR;
526 if (sign (dy) == 0 &&
528 demerit += NON_HORIZONTAL_PENALTY;
534 && sign (slur_dy) != sign (dy))
536 (beams[LEFT] || beams[RIGHT])
537 ? SAME_SLOPE_PENALTY/10 : SAME_SLOPE_PENALTY;
539 #if DEBUG_SLUR_QUANTING
540 (*scores)[i].score_card_ += to_string ("S%.2f",d);
542 (*scores)[i].score_ += demerit;
548 New_slur::get_curve (Grob*me)
552 for (SCM s= me->get_property ("control-points"); s != SCM_EOL; s = ly_cdr (s))
554 b.control_[i++] = ly_scm2offset (ly_car (s));
568 MAKE_SCHEME_CALLBACK (New_slur, height, 2);
570 New_slur::height (SCM smob, SCM ax)
572 Axis a = (Axis)ly_scm2int (ax);
573 Grob * me = unsmob_grob (smob);
574 assert (a == Y_AXIS);
576 SCM mol = me->get_uncached_stencil ();
578 if (Stencil * m = unsmob_stencil (mol))
580 return ly_interval2scm (ext);
584 Ugh should have dash-length + dash-period
586 MAKE_SCHEME_CALLBACK (New_slur, print,1);
588 New_slur::print (SCM smob)
590 Grob * me = unsmob_grob (smob);
591 if (!scm_ilength (me->get_property ("note-columns")))
597 Real base_thick = robust_scm2double (me->get_property ("thickness"), 1);
598 Real thick = base_thick * Staff_symbol_referencer::line_thickness (me);
600 Real ss = Staff_symbol_referencer::staff_space (me);
601 Bezier one = get_curve (me);
603 // get_curve may suicide
604 if (!scm_ilength (me->get_property ("note-columns")))
610 TODO: replace dashed with generic property.
612 SCM d = me->get_property ("dashed");
613 if (ly_c_number_p (d))
614 a = Lookup::dashed_slur (one, thick, thick * robust_scm2double (d, 0));
616 a = Lookup::slur (one, get_grob_direction (me) * base_thick * ss / 10.0,
619 #if DEBUG_SLUR_QUANTING
620 SCM quant_score = me->get_property ("quant-score");
621 if (// debug_beam_quanting_flag &&
622 ly_c_string_p (quant_score))
625 SCM properties = Font_interface::text_font_alist_chain (me);
627 Stencil tm = *unsmob_stencil (Text_item::interpret_markup
628 (me->get_paper ()->self_scm (), properties, quant_score));
629 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0, 0);
633 return a.smobbed_copy ();
640 ADD_INTERFACE (New_slur, "new-slur-interface",
642 "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");