]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Web-ja: update introduction
[lilypond.git] / lily / slur.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1996--2015 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_y_extent (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 (has_interface<Separation_item> (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                 && scm_is_eq (breakables[j]->get_property ("avoid-slur"),
215                               ly_symbol2scm ("inside")))
216               new_encompasses.push_back (breakables[j]);
217         }
218       else
219         new_encompasses.push_back (g);
220     }
221
222   if (Grob_array *a = unsmob<Grob_array> (me->get_object ("encompass-objects")))
223     a->set_array (new_encompasses);
224 }
225
226 Bezier
227 Slur::get_curve (Grob *me)
228 {
229   Bezier b;
230   int i = 0;
231   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
232        s = scm_cdr (s))
233     b.control_[i++] = ly_scm2offset (scm_car (s));
234
235   return b;
236 }
237
238 void
239 Slur::add_column (Grob *me, Grob *n)
240 {
241   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
242   add_bound_item (dynamic_cast<Spanner *> (me), n);
243 }
244
245 void
246 Slur::add_extra_encompass (Grob *me, Grob *n)
247 {
248   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
249 }
250
251 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
252 SCM
253 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
254 {
255   int start = robust_scm2int (start_scm, 0);
256   int end = robust_scm2int (end_scm, 0);
257   Grob *script = unsmob<Grob> (grob);
258   Grob *slur = unsmob<Grob> (script->get_object ("slur"));
259   if (!slur)
260     return offset_scm;
261
262   SCM avoid = script->get_property ("avoid-slur");
263   if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
264       && !scm_is_eq (avoid, ly_symbol2scm ("around")))
265     return offset_scm;
266
267   Real offset = robust_scm2double (offset_scm, 0.0);
268   Direction dir = get_grob_direction (script);
269   return scm_from_double (offset + dir * slur->pure_y_extent (slur, start, end).length () / 4);
270 }
271
272 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
273 SCM
274 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
275 {
276   Grob *script = unsmob<Grob> (grob);
277   Grob *slur = unsmob<Grob> (script->get_object ("slur"));
278
279   if (!slur)
280     return offset_scm;
281
282   SCM avoid = script->get_property ("avoid-slur");
283   if (!scm_is_eq (avoid, ly_symbol2scm ("outside"))
284       && !scm_is_eq (avoid, ly_symbol2scm ("around")))
285     return offset_scm;
286
287   Direction dir = get_grob_direction (script);
288   if (dir == CENTER)
289     return offset_scm;
290
291   Grob *cx = script->common_refpoint (slur, X_AXIS);
292   Grob *cy = script->common_refpoint (slur, Y_AXIS);
293
294   Bezier curve = Slur::get_curve (slur);
295
296   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
297                            slur->relative_coordinate (cy, Y_AXIS)));
298
299   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
300   Interval xext = robust_relative_extent (script, cx, X_AXIS);
301   Interval slur_wid (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
302
303   /*
304     cannot use is_empty because some 0-extent scripts
305     come up with TabStaffs.
306   */
307   if (xext.length () <= 0 || yext.length () <= 0)
308     return offset_scm;
309
310   bool contains = false;
311   for (LEFT_and_RIGHT (d))
312     contains |= slur_wid.contains (xext[d]);
313
314   if (!contains)
315     return offset_scm;
316
317   Real offset = robust_scm2double (offset_scm, 0);
318   yext.translate (offset);
319
320   /* FIXME: slur property, script property?  */
321   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
322                                          0.0);
323   yext.widen (slur_padding);
324
325   Interval exts[] = {xext, yext};
326   bool do_shift = false;
327   Real EPS = 1.0e-5;
328   if (scm_is_eq (avoid, ly_symbol2scm ("outside")))
329     {
330       for (LEFT_and_RIGHT (d))
331         {
332           Real x = minmax (-d, xext[d], curve.control_[d == LEFT ? 0 : 3][X_AXIS] + -d * EPS);
333           Real y = curve.get_other_coordinate (X_AXIS, x);
334           do_shift = y == minmax (dir, yext[-dir], y);
335           if (do_shift)
336             break;
337         }
338     }
339   else
340     {
341       for (int a = X_AXIS; a < NO_AXES; a++)
342         {
343           for (LEFT_and_RIGHT (d))
344             {
345               vector<Real> coords = curve.get_other_coordinates (Axis (a), exts[a][d]);
346               for (vsize i = 0; i < coords.size (); i++)
347                 {
348                   do_shift = exts[(a + 1) % NO_AXES].contains (coords[i]);
349                   if (do_shift)
350                     break;
351                 }
352               if (do_shift)
353                 break;
354             }
355           if (do_shift)
356             break;
357         }
358     }
359
360   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;
361
362   return scm_from_double (offset + avoidance_offset);
363 }
364
365 MAKE_SCHEME_CALLBACK (Slur, vertical_skylines, 1);
366 SCM
367 Slur::vertical_skylines (SCM smob)
368 {
369   Grob *me = unsmob<Grob> (smob);
370   vector<Box> boxes;
371
372   if (!me)
373     return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
374
375   Bezier curve = Slur::get_curve (me);
376   vsize box_count = robust_scm2vsize (me->get_property ("skyline-quantizing"), 10);
377   for (vsize i = 0; i < box_count; i++)
378     {
379       Box b;
380       b.add_point (curve.curve_point (i * 1.0 / box_count));
381       b.add_point (curve.curve_point ((i + 1) * 1.0 / box_count));
382       boxes.push_back (b);
383     }
384
385   return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
386 }
387
388 /*
389  * Used by Slur_engraver:: and Phrasing_slur_engraver::
390  */
391 void
392 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
393                                           vector<Grob *> &slurs,
394                                           vector<Grob *> &end_slurs)
395 {
396   if (slurs.empty () && end_slurs.empty ())
397     return;
398
399   Grob *e = info.grob ();
400   SCM avoid = e->get_property ("avoid-slur");
401   Grob *slur;
402   if (end_slurs.size () && !slurs.size ())
403     slur = end_slurs[0];
404   else
405     slur = slurs[0];
406
407   if (has_interface<Tie> (e)
408       || scm_is_eq (avoid, ly_symbol2scm ("inside")))
409     {
410       for (vsize i = slurs.size (); i--;)
411         add_extra_encompass (slurs[i], e);
412       for (vsize i = end_slurs.size (); i--;)
413         add_extra_encompass (end_slurs[i], e);
414       if (slur)
415         e->set_object ("slur", slur->self_scm ());
416     }
417   else if (scm_is_eq (avoid, ly_symbol2scm ("outside"))
418            || scm_is_eq (avoid, ly_symbol2scm ("around")))
419     {
420       if (slur)
421         {
422           chain_offset_callback (e,
423                                  Unpure_pure_container::make_smob (outside_slur_callback_proc,
424                                                                    pure_outside_slur_callback_proc),
425                                  Y_AXIS);
426           chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
427           e->set_object ("slur", slur->self_scm ());
428         }
429     }
430   else if (!scm_is_eq (avoid, ly_symbol2scm ("ignore")))
431     e->warning (_f ("Ignoring grob for slur: %s.  avoid-slur not set?",
432                     e->name ().c_str ()));
433 }
434
435 /*
436   A callback that will be chained together with the original cross-staff
437   value of a grob that is placed 'outside or 'around a slur. This just says
438   that any grob becomes cross-staff if it is placed 'outside or 'around a
439   cross-staff slur.
440 */
441 MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
442 SCM
443 Slur::outside_slur_cross_staff (SCM smob, SCM previous)
444 {
445   if (to_boolean (previous))
446     return previous;
447
448   Grob *me = unsmob<Grob> (smob);
449   Grob *slur = unsmob<Grob> (me->get_object ("slur"));
450
451   if (!slur)
452     return SCM_BOOL_F;
453   return slur->get_property ("cross-staff");
454 }
455
456 MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
457 SCM
458 Slur::calc_cross_staff (SCM smob)
459 {
460   Grob *me = unsmob<Grob> (smob);
461
462   extract_grob_set (me, "note-columns", cols);
463   extract_grob_set (me, "encompass-objects", extras);
464
465   for (vsize i = 0; i < cols.size (); i++)
466     {
467       if (Grob *s = Note_column::get_stem (cols[i]))
468         if (to_boolean (s->get_property ("cross-staff")))
469           return SCM_BOOL_T;
470     }
471
472   /* the separation items are dealt with in replace_breakable_encompass_objects
473      so we can ignore them here */
474   vector<Grob *> non_sep_extras;
475   for (vsize i = 0; i < extras.size (); i++)
476     if (!has_interface<Separation_item> (extras[i]))
477       non_sep_extras.push_back (extras[i]);
478
479   Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
480   common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
481
482   return scm_from_bool (common != me->get_parent (Y_AXIS));
483 }
484
485 ADD_INTERFACE (Slur,
486                "A slur."
487                "\n"
488                "The following properties may be set in the @code{details}"
489                " list.\n"
490                "\n"
491                "@table @code\n"
492                "@item region-size\n"
493                "Size of region (in staff spaces) for determining"
494                " potential endpoints in the Y direction.\n"
495                "@item head-encompass-penalty\n"
496                "Demerit to apply when note heads collide with a slur.\n"
497                "@item stem-encompass-penalty\n"
498                "Demerit to apply when stems collide with a slur.\n"
499                "@item edge-attraction-factor\n"
500                "Factor used to calculate the demerit for distances"
501                " between slur endpoints and their corresponding base"
502                " attachments.\n"
503                "@item same-slope-penalty\n"
504                "Demerit for slurs with attachment points that are"
505                " horizontally aligned.\n"
506                "@item steeper-slope-factor\n"
507                "Factor used to calculate demerit only if this slur is"
508                " not broken.\n"
509                "@item non-horizontal-penalty\n"
510                "Demerit for slurs with attachment points that are not"
511                " horizontally aligned.\n"
512                "@item max-slope\n"
513                "The maximum slope allowed for this slur.\n"
514                "@item max-slope-factor\n"
515                "Factor that calculates demerit based on the max slope.\n"
516                "@item free-head-distance\n"
517                "The amount of vertical free space that must exist"
518                " between a slur and note heads.\n"
519                "@item absolute-closeness-measure\n"
520                "Factor to calculate demerit for variance between a note"
521                " head and slur.\n"
522                "@item extra-object-collision-penalty\n"
523                "Factor to calculate demerit for extra objects that the"
524                " slur encompasses, including accidentals, fingerings, and"
525                " tuplet numbers.\n"
526                "@item accidental-collision\n"
527                "Factor to calculate demerit for @code{Accidental} objects"
528                " that the slur encompasses.  This property value replaces"
529                " the value of @code{extra-object-collision-penalty}.\n"
530                "@item extra-encompass-free-distance\n"
531                "The amount of vertical free space that must exist"
532                " between a slur and various objects it encompasses,"
533                " including accidentals, fingerings, and tuplet numbers.\n"
534                "@item extra-encompass-collision-distance\n"
535                "This detail is currently unused.\n"
536                "@item head-slur-distance-factor\n"
537                "Factor to calculate demerit for variance between a note"
538                " head and slur.\n"
539                "@item head-slur-distance-max-ratio\n"
540                "The maximum value for the ratio of distance between a"
541                " note head and slur.\n"
542                "@item gap-to-staffline-inside\n"
543                "Minimum gap inside the curve of the slur"
544                " where the slur is parallel to a staffline.\n"
545                "@item gap-to-staffline-outside\n"
546                "Minimum gap outside the curve of the slur"
547                " where the slur is parallel to a staffline.\n"
548                "@item free-slur-distance\n"
549                "The amount of vertical free space that must exist"
550                " between adjacent slurs.  This subproperty only works"
551                " for @code{PhrasingSlur}.\n"
552                "@item edge-slope-exponent\n"
553                "Factor used to calculate the demerit for the slope of"
554                " a slur near its endpoints; a larger value yields a"
555                " larger demerit.\n"
556                "@end table\n",
557
558                /* properties */
559                "annotation "
560                "avoid-slur "  /* UGH. */
561                "control-points "
562                "dash-definition "
563                "details "
564                "direction "
565                "eccentricity "
566                "encompass-objects "
567                "height-limit "
568                "inspect-quants "
569                "inspect-index "
570                "line-thickness "
571                "note-columns "
572                "positions "
573                "ratio "
574                "thickness "
575               );
576