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