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"
33 - avoid collision with staff line
34 - curve around flag/stem for x coordinate
39 struct Encompass_info {
52 TODO: put in details list.
54 const int SLUR_REGION_SIZE = 5;
55 const Real HEAD_ENCOMPASS_PENALTY = 1000.0;
56 const Real STEM_ENCOMPASS_PENALTY = 30.0;
57 const Real CLOSENESS_FACTOR = 10;
58 const Real EDGE_ATTRACTION_FACTOR = 4;
59 const Real HEAD_FREE_SPACE = 0.3;
60 const Real SAME_SLOPE_PENALTY = 20;
61 const Real STEEPER_SLOPE_FACTOR = 50;
62 const Real NON_HORIZONTAL_PENALTY = 15;
63 const Real HEAD_STRICT_FREE_SPACE = 0.2;
65 #define DEBUG_SLUR_QUANTING 1
68 Drul_array<Offset> attachment_;
70 #if DEBUG_SLUR_QUANTING
82 static void add_column (Grob *me, Grob *col);
83 DECLARE_SCHEME_CALLBACK (print, (SCM));
84 static void score_slopes (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
85 Array<Slur_score> * scores);
87 static void score_encompass (Grob * me, Grob *common[],
88 Drul_array<Offset>, Array<Slur_score> * scores);
89 static void set_interface (Grob*);
90 static bool has_interface (Grob*);
91 static Array<Offset> get_encompass_offsets (Grob *me);
92 static Bezier get_curve (Grob *me);
93 static Bezier get_bezier (Grob *me, Drul_array<Offset>);
94 static Direction get_default_dir (Grob *me);
95 DECLARE_SCHEME_CALLBACK (after_line_breaking, (SCM));
96 DECLARE_SCHEME_CALLBACK (height, (SCM,SCM));
98 static void set_end_points (Grob*);
99 static Real get_boundary_notecolumn_y (Grob *me, Direction dir);
100 static Offset broken_trend_offset (Grob *me, Direction dir);
101 static Offset get_attachment (Grob *me,Direction dir, Grob **common);
102 static void de_uglyfy (Grob *me,Slur_bezier_bow* bb, Real default_height);
103 static SCM set_extremities (Grob *me);
104 static void set_control_points (Grob *me);
105 static void check_slope (Grob *me);
106 static Encompass_info get_encompass_info (Grob *me, Grob *col, Grob **common);
110 New_slur::set_interface (Grob*me)
112 /* Copy to mutable list. */
113 me->set_property ("attachment",
114 ly_deep_copy (me->get_property ("attachment")));
118 New_slur::add_column (Grob*me, Grob*n)
120 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
121 me->add_dependency (n);
123 add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*> (n));
127 New_slur::get_encompass_info (Grob *me,
131 Grob* stem = unsmob_grob (col->get_property ("stem"));
135 Direction dir = get_grob_direction (me);
139 programming_error ("No stem for note column?");
140 ei.x_ = col->relative_coordinate (common[X_AXIS], X_AXIS);
141 ei.head_ = ei.stem_ = col->relative_coordinate (common[Y_AXIS], Y_AXIS);
144 Direction stem_dir = get_grob_direction (stem);
146 if (Grob *head = Note_column::first_head (col))
147 ei.x_ = head->extent (common[X_AXIS], X_AXIS).center ();
149 ei.x_ = col->extent (common[X_AXIS], X_AXIS).center ();
151 Grob * h = Stem::extremal_heads (stem)[Direction (dir)];
154 ei.head_ = ei.stem_ = col->relative_coordinate (common[Y_AXIS], Y_AXIS);
158 ei.head_ = h->extent (common[Y_AXIS], Y_AXIS)[dir];
160 if ((stem_dir == dir)
161 && !stem->extent (stem, Y_AXIS).is_empty ())
163 ei.stem_ = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
173 New_slur::get_default_dir (Grob*me)
175 Link_array<Grob> encompasses =
176 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
179 for (int i=0; i < encompasses.size (); i ++)
181 if (Note_column::dir (encompasses[i]) < 0)
190 MAKE_SCHEME_CALLBACK (New_slur, after_line_breaking,1);
192 New_slur::after_line_breaking (SCM smob)
194 Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
195 if (!scm_ilength (me->get_property ("note-columns")))
198 return SCM_UNSPECIFIED;
201 if (!get_grob_direction (me))
202 set_grob_direction (me, get_default_dir (me));
205 if (!Note_column::has_interface (me->get_bound (LEFT))
206 || !Note_column::has_interface (me->get_bound (RIGHT)))
207 me->suicide (); // fixme.
211 return SCM_UNSPECIFIED;
215 New_slur::get_bezier (Grob *me, Drul_array<Offset> extremes)
217 // SCM details = me->get_property ("details");
218 SCM h_inf_scm = me->get_property ("height-limit");
219 SCM r_0_scm = me->get_property ("ratio");
220 Real staff_space = Staff_symbol_referencer::staff_space ((Grob*)me);
222 Real r_0 = robust_scm2double (r_0_scm, 1);
223 Real h_inf = staff_space * ly_scm2double (h_inf_scm);
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];
278 Real y = h->extent (common[Y_AXIS], Y_AXIS)[dir];
280 y += dir * 0.5 * staff_space;
281 int p = Staff_symbol_referencer::get_position (h) + 2*dir;
284 if (Staff_symbol_referencer::on_staffline (h, p))
288 y += staff_space * dir / 10 ;
290 Grob * fh = Note_column::first_head (extremes[d]);
291 Real x = fh->extent (common[X_AXIS], X_AXIS).linear_combination (CENTER);
293 if (get_grob_direction (stem) == dir
296 x -= d * fh->extent(fh, X_AXIS).length ();
299 base_attachment[d] = Offset (x, y);
301 } while (flip (&d) != LEFT);
303 Drul_array<Real> staff_offsets;
307 staff_offsets[d] = staves[d]->relative_coordinate (common[Y_AXIS], Y_AXIS);
308 end_ys[d] = dir * ((dir * (base_attachment[d][Y_AXIS] + 4.0 *dir)) >?
309 (dir * (dir + extremes[d]->extent(common[Y_AXIS],Y_AXIS)[dir])));
310 } while (flip (&d) != LEFT);
312 Array<Slur_score> scores;
314 Drul_array<Offset> os;
317 os[LEFT] = base_attachment[LEFT];
319 for (int i = 0; dir * os[LEFT][Y_AXIS] < dir * end_ys[LEFT]; i++)
321 os[RIGHT] = base_attachment[RIGHT];
322 for (int j = 0; dir *os[RIGHT][Y_AXIS] < dir * end_ys[RIGHT]; j++)
329 Real incr = dir * staff_space;
330 if (Staff_symbol_referencer::staff_radius (staves[RIGHT])
331 < fabs ((os[RIGHT][Y_AXIS] - staff_offsets[RIGHT]) / staff_space))
334 os[RIGHT][Y_AXIS] += incr;
337 Real incr = dir * staff_space;
338 if (Staff_symbol_referencer::staff_radius (staves[LEFT])
339 < fabs ((os[LEFT][Y_AXIS] - staff_offsets[LEFT]) / staff_space))
342 os[LEFT][Y_AXIS] += incr;
346 score_encompass (me, common, base_attachment, &scores);
347 score_slopes (me, common, base_attachment, &scores);
351 for (int i = scores.size (); i--;)
353 if (scores[i].score_ < opt)
355 opt = scores[i].score_;
360 Bezier b (get_bezier (me, scores.size () ? scores[opt_idx].attachment_ : base_attachment));
362 SCM controls = SCM_EOL;
363 for (int i = 4; i--;)
365 Offset o = b.control_[i] -
366 Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
367 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
369 controls = scm_cons (ly_offset2scm (o), controls);
372 me->set_property ("control-points", controls);
374 #if DEBUG_SLUR_QUANTING
375 scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
378 me->set_property ("quant-score",
379 scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
385 New_slur::score_encompass (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
386 Array<Slur_score> * scores)
388 Link_array<Grob> encompasses =
389 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
390 Direction dir = get_grob_direction (me);
392 Array<Encompass_info> infos;
395 int last = encompasses.size () - 2;
397 for (int i = first; i <= last; i++)
398 infos.push (get_encompass_info (me, encompasses[i], common));
400 for (int i =0 ; i < scores->size (); i++)
402 Bezier bez (get_bezier (me, scores->elem (i).attachment_));
404 for (int j = 0; j < infos.size(); j++)
406 Real x = infos[j].x_;
408 if (!(x < scores->elem (i).attachment_[RIGHT][X_AXIS]
409 &&x > scores->elem (i).attachment_[LEFT][X_AXIS]))
412 Real y = bez.get_other_coordinate (X_AXIS, x);
414 if (dir * (y - infos[j].head_) < 0)
415 demerit += HEAD_ENCOMPASS_PENALTY;
417 if (dir * (y - infos[j].stem_) < 0)
418 demerit += STEM_ENCOMPASS_PENALTY;
422 ext.add_point (infos[j].stem_);
423 ext.add_point (infos[j].head_);
425 demerit += - CLOSENESS_FACTOR * (dir * (y - (ext[dir] + dir * HEAD_FREE_SPACE)) <? 0) /
433 EDGE_ATTRACTION_FACTOR
434 * fabs (scores->elem (i).attachment_[d][Y_AXIS] - base_attach[d][Y_AXIS]);
435 } while (flip (&d) != LEFT);
437 #if DEBUG_SLUR_QUANTING
438 (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
441 (*scores)[i].score_ += demerit;
447 New_slur::score_slopes (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
448 Array<Slur_score> * scores)
450 Link_array<Grob> columns =
451 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
453 Drul_array<Grob *> extremes (columns[0], columns.top ());
454 Direction dir = get_grob_direction (me);
459 Grob *stem = Note_column::get_stem (extremes [d]);
460 ys[d] = Stem::extremal_heads (stem)[Direction (dir)]
461 ->relative_coordinate (common[Y_AXIS], Y_AXIS);
462 } while (flip (&d) != LEFT);
465 Real dy = ys[RIGHT] - ys[LEFT];
469 for (int i =0 ; i < scores->size (); i++)
471 Real slur_dy = (*scores)[i].attachment_[RIGHT][Y_AXIS]
472 - (*scores)[i].attachment_[LEFT][Y_AXIS];
476 demerit += STEEPER_SLOPE_FACTOR * (dir * (fabs (slur_dy) - fabs (dy)) >? 0);
477 if (sign (dy) == 0 &&
479 demerit += NON_HORIZONTAL_PENALTY;
482 && sign (slur_dy) != sign (dy))
483 demerit += SAME_SLOPE_PENALTY;
485 #if DEBUG_SLUR_QUANTING
486 (*scores)[i].score_card_ += to_string ("S%.2f",d);
488 (*scores)[i].score_ += demerit;
494 New_slur::get_curve (Grob*me)
498 for (SCM s= me->get_property ("control-points"); s != SCM_EOL; s = ly_cdr (s))
500 b.control_[i++] = ly_scm2offset (ly_car (s));
514 MAKE_SCHEME_CALLBACK (New_slur, height, 2);
516 New_slur::height (SCM smob, SCM ax)
518 Axis a = (Axis)ly_scm2int (ax);
519 Grob * me = unsmob_grob (smob);
520 assert (a == Y_AXIS);
522 SCM mol = me->get_uncached_stencil ();
524 if (Stencil * m = unsmob_stencil (mol))
526 return ly_interval2scm (ext);
530 Ugh should have dash-length + dash-period
532 MAKE_SCHEME_CALLBACK (New_slur, print,1);
534 New_slur::print (SCM smob)
536 Grob * me = unsmob_grob (smob);
537 if (!scm_ilength (me->get_property ("note-columns")))
543 Real base_thick = robust_scm2double (me->get_property ("thickness"), 1);
544 Real thick = base_thick * Staff_symbol_referencer::line_thickness (me);
546 Real ss = Staff_symbol_referencer::staff_space (me);
547 Bezier one = get_curve (me);
549 // get_curve may suicide
550 if (!scm_ilength (me->get_property ("note-columns")))
556 TODO: replace dashed with generic property.
558 SCM d = me->get_property ("dashed");
559 if (ly_c_number_p (d))
560 a = Lookup::dashed_slur (one, thick, thick * robust_scm2double (d, 0));
562 a = Lookup::slur (one, get_grob_direction (me) * base_thick * ss / 10.0,
565 #if DEBUG_SLUR_QUANTING
566 SCM quant_score = me->get_property ("quant-score");
567 if (// debug_beam_quanting_flag &&
568 ly_c_string_p (quant_score))
571 SCM properties = Font_interface::text_font_alist_chain (me);
573 Stencil tm = *unsmob_stencil (Text_item::interpret_markup
574 (me->get_paper ()->self_scm (), properties, quant_score));
575 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0, 0);
579 return a.smobbed_copy ();
586 ADD_INTERFACE (New_slur, "new-slur-interface",
588 "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");