]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
9f12df878c20099e56b426d8d5b8263b33b6a990
[lilypond.git] / lily / slur.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1996--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5   Jan Nieuwenhuizen <janneke@gnu.org>
6
7   LilyPond is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   LilyPond is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "slur.hh"
22 #include "grob-info.hh"
23 #include "grob-array.hh"
24 #include "beam.hh"
25 #include "bezier.hh"
26 #include "directional-element-interface.hh"
27 #include "font-interface.hh"
28 #include "item.hh"
29 #include "pointer-group-interface.hh"
30 #include "lookup.hh"
31 #include "main.hh"              // DEBUG_SLUR_SCORING
32 #include "note-column.hh"
33 #include "output-def.hh"
34 #include "skyline-pair.hh"
35 #include "spanner.hh"
36 #include "staff-symbol-referencer.hh"
37 #include "stem.hh"
38 #include "text-interface.hh"
39 #include "tie.hh"
40 #include "warn.hh"
41 #include "slur-scoring.hh"
42 #include "separation-item.hh"
43 #include "unpure-pure-container.hh"
44 #include "international.hh"
45
46 MAKE_SCHEME_CALLBACK (Slur, calc_direction, 1)
47 SCM
48 Slur::calc_direction (SCM smob)
49 {
50   Grob *me = unsmob_grob (smob);
51   vector<Grob *> encompasses;
52   extract_grob_set (me, "note-columns", ro_encompasses);
53   encompasses.insert (encompasses.end (), ro_encompasses.begin (), ro_encompasses.end ());
54   if (Grob *other_half = unsmob_grob (me->get_object ("other-half")))
55     {
56       extract_grob_set (other_half, "note-columns", oh_encompasses);
57       encompasses.insert (encompasses.end (), oh_encompasses.begin (), oh_encompasses.end ());
58     }
59
60   if (encompasses.empty ())
61     {
62       me->suicide ();
63       return SCM_BOOL_F;
64     }
65
66   Direction d = DOWN;
67   for (vsize i = 0; i < encompasses.size (); i++)
68     {
69       if (Note_column::dir (encompasses[i]) < 0)
70         {
71           d = UP;
72           break;
73         }
74     }
75   return scm_from_int (d);
76 }
77
78 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
79 SCM
80 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
81 {
82   /*
83     Note that this estimation uses a rote add-on of 0.5 to the
84     highest encompassed note-head for a slur estimate.  This is,
85     in most cases, shorter than the actual slur.
86
87     Ways to improve this could include:
88     -- adding extra height for scripts that avoid slurs on the inside
89     -- adding extra height for the "bulge" in a slur above a note head
90   */
91   Grob *me = unsmob_grob (smob);
92   int start = scm_to_int (start_scm);
93   int end = scm_to_int (end_scm);
94   Direction dir = get_grob_direction (me);
95
96   extract_grob_set (me, "note-columns", encompasses);
97   Interval ret;
98   ret.set_empty ();
99
100   Grob *parent = me->get_parent (Y_AXIS);
101   Drul_array<Real> extremal_heights (infinity_f, -infinity_f);
102   if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
103     /* this could happen if, for example, we are a cross-staff slur.
104        in this case, we want to be ignored */
105     return ly_interval2scm (Interval ());
106
107   for (vsize i = 0; i < encompasses.size (); i++)
108     {
109       Interval d = encompasses[i]->pure_height (parent, start, end);
110       if (!d.is_empty ())
111         {
112           for (DOWN_and_UP (downup))
113             ret.add_point (d[dir]);
114
115           if (extremal_heights[LEFT] == infinity_f)
116             extremal_heights[LEFT] = d[dir];
117           extremal_heights[RIGHT] = d[dir];
118         }
119     }
120
121   if (ret.is_empty ())
122     return ly_interval2scm (Interval ());
123
124   Interval extremal_span;
125   extremal_span.set_empty ();
126   for (LEFT_and_RIGHT (d))
127     extremal_span.add_point (extremal_heights[d]);
128   ret[-dir] = minmax (dir, extremal_span[-dir], ret[-dir]);
129
130   /*
131     The +0.5 comes from the fact that we try to place a slur
132     0.5 staff spaces from the note-head.
133     (see Slur_score_state.get_base_attachments ())
134   */
135   ret += 0.5 * dir;
136   return ly_interval2scm (ret);
137 }
138
139 MAKE_SCHEME_CALLBACK (Slur, height, 1);
140 SCM
141 Slur::height (SCM smob)
142 {
143   Grob *me = unsmob_grob (smob);
144
145   // FIXME uncached
146   Stencil *m = me->get_stencil ();
147   return m ? ly_interval2scm (m->extent (Y_AXIS))
148          : ly_interval2scm (Interval ());
149 }
150
151 MAKE_SCHEME_CALLBACK (Slur, print, 1);
152 SCM
153 Slur::print (SCM smob)
154 {
155   Grob *me = unsmob_grob (smob);
156   extract_grob_set (me, "note-columns", encompasses);
157   if (encompasses.empty ())
158     {
159       me->suicide ();
160       return SCM_EOL;
161     }
162
163   Real staff_thick = Staff_symbol_referencer::line_thickness (me);
164   Real base_thick = staff_thick
165                     * robust_scm2double (me->get_property ("thickness"), 1);
166   Real line_thick = staff_thick
167                     * robust_scm2double (me->get_property ("line-thickness"), 1);
168
169   Bezier one = get_curve (me);
170   Stencil a;
171
172   SCM dash_definition = me->get_property ("dash-definition");
173   a = Lookup::slur (one,
174                     get_grob_direction (me) * base_thick,
175                     line_thick,
176                     dash_definition);
177
178 #if DEBUG_SLUR_SCORING
179   SCM annotation = me->get_property ("annotation");
180   if (scm_is_string (annotation))
181     {
182       string str;
183       SCM properties = Font_interface::text_font_alist_chain (me);
184
185       if (!scm_is_number (me->get_property ("font-size")))
186         properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
187                                properties);
188
189       Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
190                                     (me->layout ()->self_scm (), properties,
191                                      annotation));
192       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
193     }
194 #endif
195
196   return a.smobbed_copy ();
197 }
198
199 /*
200   it would be better to do this at engraver level, but that is
201   fragile, as the breakable items are generated on staff level, at
202   which point slur starts and ends have to be tracked
203 */
204 void
205 Slur::replace_breakable_encompass_objects (Grob *me)
206 {
207   extract_grob_set (me, "encompass-objects", extra_objects);
208   vector<Grob *> new_encompasses;
209
210   for (vsize i = 0; i < extra_objects.size (); i++)
211     {
212       Grob *g = extra_objects[i];
213
214       if (Separation_item::has_interface (g))
215         {
216           extract_grob_set (g, "elements", breakables);
217           for (vsize j = 0; j < breakables.size (); j++)
218             /* if we encompass a separation-item that spans multiple staves,
219                we filter out the grobs that don't belong to our staff */
220             if (me->common_refpoint (breakables[j], Y_AXIS) == me->get_parent (Y_AXIS)
221                 && breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
222               new_encompasses.push_back (breakables[j]);
223         }
224       else
225         new_encompasses.push_back (g);
226     }
227
228   SCM encompass_scm = me->get_object ("encompass-objects");
229   if (Grob_array::unsmob (encompass_scm))
230     {
231       vector<Grob *> &arr
232         = unsmob_grob_array (encompass_scm)->array_reference ();
233       arr = new_encompasses;
234     }
235 }
236
237 Bezier
238 Slur::get_curve (Grob *me)
239 {
240   Bezier b;
241   int i = 0;
242   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
243        s = scm_cdr (s))
244     b.control_[i++] = ly_scm2offset (scm_car (s));
245
246   return b;
247 }
248
249 void
250 Slur::add_column (Grob *me, Grob *n)
251 {
252   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
253   add_bound_item (dynamic_cast<Spanner *> (me), n);
254 }
255
256 void
257 Slur::add_extra_encompass (Grob *me, Grob *n)
258 {
259   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
260 }
261
262 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
263 SCM
264 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
265 {
266   int start = robust_scm2int (start_scm, 0);
267   int end = robust_scm2int (end_scm, 0);
268   Grob *script = unsmob_grob (grob);
269   Grob *slur = unsmob_grob (script->get_object ("slur"));
270   if (!slur)
271     return offset_scm;
272
273   SCM avoid = script->get_property ("avoid-slur");
274   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
275     return offset_scm;
276
277   Real offset = robust_scm2double (offset_scm, 0.0);
278   Direction dir = get_grob_direction (script);
279   return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
280 }
281
282 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
283 SCM
284 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
285 {
286   Grob *script = unsmob_grob (grob);
287   Grob *slur = unsmob_grob (script->get_object ("slur"));
288
289   if (!slur)
290     return offset_scm;
291
292   SCM avoid = script->get_property ("avoid-slur");
293   if (avoid != ly_symbol2scm ("outside")
294       && avoid != ly_symbol2scm ("around"))
295     return offset_scm;
296
297   Direction dir = get_grob_direction (script);
298   if (dir == CENTER)
299     return offset_scm;
300
301   Grob *cx = script->common_refpoint (slur, X_AXIS);
302   Grob *cy = script->common_refpoint (slur, Y_AXIS);
303
304   Bezier curve = Slur::get_curve (slur);
305
306   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
307                            slur->relative_coordinate (cy, Y_AXIS)));
308
309   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
310   Interval xext = robust_relative_extent (script, cx, X_AXIS);
311   Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
312
313   /*
314     cannot use is_empty because some 0-extent scripts
315     come up with TabStaffs.
316   */
317   if (xext.length () <= 0 || yext.length () <= 0)
318     return offset_scm;
319
320   bool contains = false;
321   for (LEFT_and_RIGHT (d))
322     contains |= slur_wid.contains (xext[d]);
323
324   if (!contains)
325     return offset_scm;
326
327   Real offset = robust_scm2double (offset_scm, 0);
328   yext.translate (offset);
329
330   /* FIXME: slur property, script property?  */
331   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
332                                          0.0);
333   yext.widen (slur_padding);
334
335   Interval exts[] = {xext, yext};
336   bool do_shift = false;
337   Real EPS = 1.0e-5;
338   if (avoid == ly_symbol2scm ("outside"))
339     {
340       for (LEFT_and_RIGHT (d))
341         {
342           Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
343           Real y = curve.get_other_coordinate (X_AXIS, x);
344           do_shift = y == minmax (dir, yext[-dir], y);
345           if (do_shift)
346             break;
347         }
348     }
349   else
350     {
351       for (int a = X_AXIS; a < NO_AXES; a++)
352         {
353           for (LEFT_and_RIGHT (d))
354             {
355               vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
356               for (vsize i = 0; i < coords.size (); i++)
357                 {
358                   do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
359                   if (do_shift)
360                     break;
361                 }
362               if (do_shift)
363                 break;
364             }
365           if (do_shift)
366             break;
367         }
368     }
369
370   Real avoidance_offset = do_shift ? curve.minmax (X_AXIS, max (xext[LEFT], curve.control_[0][X_AXIS] + EPS), min (xext[RIGHT], curve.control_[3][X_AXIS] - EPS), dir) - yext[-dir] : 0.0;
371
372   return scm_from_double (offset + avoidance_offset);
373 }
374
375 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
376 SCM
377 Slur::vertical_skylines (SCM smob)
378 {
379   Grob *me = unsmob_grob (smob);
380   vector<Box> boxes;
381
382   if (!me)
383     return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
384
385   Bezier curve = Slur::get_curve (me);
386   vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
387   for (vsize i = 0; i < box_count; i++)
388     {
389       Box b;
390       b.add_point (curve.curve_point (i * 1.0 / box_count));
391       b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
392       boxes.push_back (b);
393     }
394
395   return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
396 }
397
398 /*
399  * Used by Slur_engraver:: and Phrasing_slur_engraver::
400  */
401 void
402 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
403                                           vector<Grob *> &slurs,
404                                           vector<Grob *> &end_slurs)
405 {
406   if (slurs.empty () && end_slurs.empty ())
407     return;
408
409   Grob *e = info.grob ();
410   SCM avoid = e->get_property ("avoid-slur");
411   Grob *slur;
412   if (end_slurs.size () && !slurs.size ())
413     slur = end_slurs[0];
414   else
415     slur = slurs[0];
416
417   if (Tie::has_interface (e)
418       || avoid == ly_symbol2scm ("inside"))
419     {
420       for (vsize i = slurs.size (); i--;)
421         add_extra_encompass (slurs[i], e);
422       for (vsize i = end_slurs.size (); i--;)
423         add_extra_encompass (end_slurs[i], e);
424       if (slur)
425         e->set_object ("slur", slur->self_scm ());
426     }
427   else if (avoid == ly_symbol2scm ("outside")
428            || avoid == ly_symbol2scm ("around"))
429     {
430       if (slur)
431         {
432           chain_offset_callback (e,
433                                  ly_make_unpure_pure_container (outside_slur_callback_proc,
434                                                                 pure_outside_slur_callback_proc),
435                                  Y_AXIS);
436           chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
437           e->set_object ("slur", slur->self_scm ());
438         }
439     }
440   else if (avoid != ly_symbol2scm ("ignore"))
441     e->warning (_f ("Ignoring grob for slur: %s.  avoid-slur not set?",
442                     e->name ().c_str ()));
443 }
444
445 /*
446   A callback that will be chained together with the original cross-staff
447   value of a grob that is placed 'outside or 'around a slur. This just says
448   that any grob becomes cross-staff if it is placed 'outside or 'around a
449   cross-staff slur.
450 */
451 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
452 SCM
453 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
454 {
455   if (previous == SCM_BOOL_T)
456     return previous;
457
458   Grob *me = unsmob_grob (smob);
459   Grob *slur = unsmob_grob (me->get_object ("slur"));
460
461   if (!slur)
462     return SCM_BOOL_F;
463   return slur->get_property ("cross-staff");
464 }
465
466 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
467 SCM
468 Slur::calc_cross_staff (SCM smob)
469 {
470   Grob *me = unsmob_grob (smob);
471
472   extract_grob_set (me, "note-columns", cols);
473   extract_grob_set (me, "encompass-objects", extras);
474
475   for (vsize i = 0; i < cols.size (); i++)
476     {
477       if (Grob *s = Note_column::get_stem (cols[i]))
478         if (to_boolean (s->get_property ("cross-staff")))
479           return SCM_BOOL_T;
480     }
481
482   /* the separation items are dealt with in replace_breakable_encompass_objects
483      so we can ignore them here */
484   vector<Grob *> non_sep_extras;
485   for (vsize i = 0; i < extras.size (); i++)
486     if (!Separation_item::has_interface (extras[i]))
487       non_sep_extras.push_back (extras[i]);
488
489   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
490   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
491
492   return scm_from_bool (common != me->get_parent (Y_AXIS));
493 }
494
495 ADD_INTERFACE (Slur,
496                "A slur."
497                "\n"
498                "The following properties may be set in the @code{details}"
499                " list.\n"
500                "\n"
501                "@table @code\n"
502                "@item region-size\n"
503                "Size of region (in staff spaces) for determining"
504                " potential endpoints in the Y direction.\n"
505                "@item head-encompass-penalty\n"
506                "Demerit to apply when note heads collide with a slur.\n"
507                "@item stem-encompass-penalty\n"
508                "Demerit to apply when stems collide with a slur.\n"
509                "@item edge-attraction-factor\n"
510                "Factor used to calculate the demerit for distances"
511                " between slur endpoints and their corresponding base"
512                " attachments.\n"
513                "@item same-slope-penalty\n"
514                "Demerit for slurs with attachment points that are"
515                " horizontally aligned.\n"
516                "@item steeper-slope-factor\n"
517                "Factor used to calculate demerit only if this slur is"
518                " not broken.\n"
519                "@item non-horizontal-penalty\n"
520                "Demerit for slurs with attachment points that are not"
521                " horizontally aligned.\n"
522                "@item max-slope\n"
523                "The maximum slope allowed for this slur.\n"
524                "@item max-slope-factor\n"
525                "Factor that calculates demerit based on the max slope.\n"
526                "@item free-head-distance\n"
527                "The amount of vertical free space that must exist"
528                " between a slur and note heads.\n"
529                "@item absolute-closeness-measure\n"
530                "Factor to calculate demerit for variance between a note"
531                " head and slur.\n"
532                "@item extra-object-collision-penalty\n"
533                "Factor to calculate demerit for extra objects that the"
534                " slur encompasses, including accidentals, fingerings, and"
535                " tuplet numbers.\n"
536                "@item accidental-collision\n"
537                "Factor to calculate demerit for @code{Accidental} objects"
538                " that the slur encompasses.  This property value replaces"
539                " the value of @code{extra-object-collision-penalty}.\n"
540                "@item extra-encompass-free-distance\n"
541                "The amount of vertical free space that must exist"
542                " between a slur and various objects it encompasses,"
543                " including accidentals, fingerings, and tuplet numbers.\n"
544                "@item extra-encompass-collision-distance\n"
545                "This detail is currently unused.\n"
546                "@item head-slur-distance-factor\n"
547                "Factor to calculate demerit for variance between a note"
548                " head and slur.\n"
549                "@item head-slur-distance-max-ratio\n"
550                "The maximum value for the ratio of distance between a"
551                " note head and slur.\n"
552                "@item free-slur-distance\n"
553                "The amount of vertical free space that must exist"
554                " between adjacent slurs.  This subproperty only works"
555                " for @code{PhrasingSlur}.\n"
556                "@item edge-slope-exponent\n"
557                "Factor used to calculate the demerit for the slope of"
558                " a slur near its endpoints; a larger value yields a"
559                " larger demerit.\n"
560                "@end table\n",
561
562                /* properties */
563                "annotation "
564                "avoid-slur "  /* UGH. */
565                "control-points "
566                "dash-definition "
567                "details "
568                "direction "
569                "eccentricity "
570                "encompass-objects "
571                "height-limit "
572                "inspect-quants "
573                "inspect-index "
574                "line-thickness "
575                "note-columns "
576                "positions "
577                "ratio "
578                "thickness "
579               );
580