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