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