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];
279 && get_grob_direction (stem) == dir
280 && Stem::get_beam (stem))
282 y = stem->extent (common[Y_AXIS], Y_AXIS)[dir];
286 y = h->extent (common[Y_AXIS], Y_AXIS)[dir];
289 y += dir * 0.5 * staff_space;
291 Grob * staff = Staff_symbol_referencer::get_staff_symbol (h);
292 Real pos = 2.0 * (y - staff->relative_coordinate (common[Y_AXIS], Y_AXIS))
293 / Staff_symbol::staff_space (staff);
299 if (fabs (pos - round (pos)) < 0.2
300 && Staff_symbol_referencer::on_staffline (h, (int) rint (pos))
301 && Staff_symbol_referencer::line_count (h) -1 >= rint (pos)
303 y += staff_space * dir / 10 ;
307 Grob * fh = Note_column::first_head (extremes[d]);
308 Real x = fh->extent (common[X_AXIS], X_AXIS).linear_combination (CENTER);
310 if (get_grob_direction (stem) == dir
313 x -= d * fh->extent(fh, X_AXIS).length ();
316 base_attachment[d] = Offset (x, y);
318 } while (flip (&d) != LEFT);
320 Drul_array<Real> staff_offsets;
324 staff_offsets[d] = staves[d]->relative_coordinate (common[Y_AXIS], Y_AXIS);
325 end_ys[d] = dir * ((dir * (base_attachment[d][Y_AXIS] + 4.0 *dir)) >?
326 (dir * (dir + extremes[d]->extent(common[Y_AXIS],Y_AXIS)[dir])));
327 } while (flip (&d) != LEFT);
329 Array<Slur_score> scores;
331 Drul_array<Offset> os;
334 os[LEFT] = base_attachment[LEFT];
336 for (int i = 0; dir * os[LEFT][Y_AXIS] < dir * end_ys[LEFT]; i++)
338 os[RIGHT] = base_attachment[RIGHT];
339 for (int j = 0; dir *os[RIGHT][Y_AXIS] < dir * end_ys[RIGHT]; j++)
346 Real incr = dir * staff_space;
347 if (Staff_symbol_referencer::staff_radius (staves[RIGHT])
348 < fabs ((os[RIGHT][Y_AXIS] - staff_offsets[RIGHT]) / staff_space))
351 os[RIGHT][Y_AXIS] += incr;
354 Real incr = dir * staff_space;
355 if (Staff_symbol_referencer::staff_radius (staves[LEFT])
356 < fabs ((os[LEFT][Y_AXIS] - staff_offsets[LEFT]) / staff_space))
359 os[LEFT][Y_AXIS] += incr;
363 Real r_0 = robust_scm2double (me->get_property ("ratio"), 1);
364 Real h_inf = staff_space * ly_scm2double (me->get_property ("height-limit"));
365 for (int i = scores.size(); i-- ;)
367 scores[i].curve_ = get_bezier (me, scores[i].attachment_,
372 score_encompass (me, common, base_attachment, &scores);
373 score_slopes (me, common, base_attachment, &scores);
377 for (int i = scores.size (); i--;)
379 if (scores[i].score_ < opt)
381 opt = scores[i].score_;
386 Bezier const &b = scores[opt_idx].curve_;
388 SCM controls = SCM_EOL;
389 for (int i = 4; i--;)
391 Offset o = b.control_[i] -
392 Offset (me->relative_coordinate (common[X_AXIS], X_AXIS),
393 me->relative_coordinate (common[Y_AXIS], Y_AXIS));
395 controls = scm_cons (ly_offset2scm (o), controls);
398 me->set_property ("control-points", controls);
400 #if DEBUG_SLUR_QUANTING
401 scores[opt_idx].score_card_ += to_string ("i%d", opt_idx);
404 me->set_property ("quant-score",
405 scm_makfrom0str (scores[opt_idx].score_card_.to_str0 ()));
411 New_slur::score_encompass (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
412 Array<Slur_score> * scores)
414 Link_array<Grob> encompasses =
415 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
416 Direction dir = get_grob_direction (me);
418 Array<Encompass_info> infos;
419 Drul_array<Grob *> extremes (encompasses[0], encompasses.top ());
422 int last = encompasses.size () - 2;
424 for (int i = first; i <= last; i++)
425 infos.push (get_encompass_info (me, encompasses[i], common));
427 Drul_array<Grob*> stems;
430 Grob *stem = Note_column::get_stem (extremes [d]);
432 } while (flip (&d) != LEFT);
436 for (int i =0 ; i < scores->size (); i++)
438 Bezier const &bez (scores->elem (i).curve_);
440 for (int j = 0; j < infos.size(); j++)
442 Real x = infos[j].x_;
444 if (!(x < scores->elem (i).attachment_[RIGHT][X_AXIS]
445 &&x > scores->elem (i).attachment_[LEFT][X_AXIS]))
448 Real y = bez.get_other_coordinate (X_AXIS, x);
450 if (dir * (y - infos[j].head_) < 0)
451 demerit += HEAD_ENCOMPASS_PENALTY;
453 if (dir * (y - infos[j].stem_) < 0)
454 demerit += STEM_ENCOMPASS_PENALTY;
458 ext.add_point (infos[j].stem_);
459 ext.add_point (infos[j].head_);
461 demerit += - CLOSENESS_FACTOR * (dir * (y - (ext[dir] + dir * HEAD_FREE_SPACE)) <? 0) /
470 EDGE_ATTRACTION_FACTOR
471 * fabs (scores->elem (i).attachment_[d][Y_AXIS] - base_attach[d][Y_AXIS]);
472 if (get_grob_direction (stems[d]) == dir)
476 } while (flip (&d) != LEFT);
478 #if DEBUG_SLUR_QUANTING
479 (*scores)[i].score_card_ += to_string ("E%.2f", demerit);
482 (*scores)[i].score_ += demerit;
488 New_slur::score_slopes (Grob * me, Grob *common[], Drul_array<Offset> base_attach,
489 Array<Slur_score> * scores)
491 Link_array<Grob> columns =
492 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
494 Drul_array<Grob *> extremes (columns[0], columns.top ());
495 Direction dir = get_grob_direction (me);
499 Drul_array<Direction> stem_dirs;
500 Drul_array<bool> beams;
502 Grob *stem = Note_column::get_stem (extremes [d]);
503 ys[d] = Stem::extremal_heads (stem)[Direction (dir)]
504 ->relative_coordinate (common[Y_AXIS], Y_AXIS);
506 stem_dirs[d] = get_grob_direction (stem);
507 beams[d] = Stem::get_beam (stem);
508 } while (flip (&d) != LEFT);
510 Real dx = extremes[RIGHT]->relative_coordinate (common[X_AXIS],X_AXIS)
511 - extremes[LEFT]->relative_coordinate (common[X_AXIS],X_AXIS);
513 Real dy = ys[RIGHT] - ys[LEFT];
514 for (int i =0 ; i < scores->size (); i++)
516 Real slur_dy = (*scores)[i].attachment_[RIGHT][Y_AXIS]
517 - (*scores)[i].attachment_[LEFT][Y_AXIS];
522 if(! (beams[LEFT] || beams[RIGHT]))
523 demerit += STEEPER_SLOPE_FACTOR * (dir * (fabs (slur_dy) - fabs (dy)) >? 0);
525 demerit += ((fabs (slur_dy/dx) - MAX_SLOPE)>?0) * MAX_SLOPE_FACTOR;
527 if (sign (dy) == 0 &&
529 demerit += NON_HORIZONTAL_PENALTY;
535 && sign (slur_dy) != sign (dy))
537 (beams[LEFT] || beams[RIGHT])
538 ? SAME_SLOPE_PENALTY/10 : SAME_SLOPE_PENALTY;
540 #if DEBUG_SLUR_QUANTING
541 (*scores)[i].score_card_ += to_string ("S%.2f",d);
543 (*scores)[i].score_ += demerit;
549 New_slur::get_curve (Grob*me)
553 for (SCM s= me->get_property ("control-points"); s != SCM_EOL; s = ly_cdr (s))
555 b.control_[i++] = ly_scm2offset (ly_car (s));
569 MAKE_SCHEME_CALLBACK (New_slur, height, 2);
571 New_slur::height (SCM smob, SCM ax)
573 Axis a = (Axis)ly_scm2int (ax);
574 Grob * me = unsmob_grob (smob);
575 assert (a == Y_AXIS);
577 SCM mol = me->get_uncached_stencil ();
579 if (Stencil * m = unsmob_stencil (mol))
581 return ly_interval2scm (ext);
585 Ugh should have dash-length + dash-period
587 MAKE_SCHEME_CALLBACK (New_slur, print,1);
589 New_slur::print (SCM smob)
591 Grob * me = unsmob_grob (smob);
592 if (!scm_ilength (me->get_property ("note-columns")))
598 Real base_thick = robust_scm2double (me->get_property ("thickness"), 1);
599 Real thick = base_thick * Staff_symbol_referencer::line_thickness (me);
601 Real ss = Staff_symbol_referencer::staff_space (me);
602 Bezier one = get_curve (me);
604 // get_curve may suicide
605 if (!scm_ilength (me->get_property ("note-columns")))
611 TODO: replace dashed with generic property.
613 SCM d = me->get_property ("dashed");
614 if (ly_c_number_p (d))
615 a = Lookup::dashed_slur (one, thick, thick * robust_scm2double (d, 0));
617 a = Lookup::slur (one, get_grob_direction (me) * base_thick * ss / 10.0,
620 #if DEBUG_SLUR_QUANTING
621 SCM quant_score = me->get_property ("quant-score");
622 if (// debug_beam_quanting_flag &&
623 ly_c_string_p (quant_score))
626 SCM properties = Font_interface::text_font_alist_chain (me);
628 Stencil tm = *unsmob_stencil (Text_item::interpret_markup
629 (me->get_paper ()->self_scm (), properties, quant_score));
630 a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0, 0);
634 return a.smobbed_copy ();
641 ADD_INTERFACE (New_slur, "new-slur-interface",
643 "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");