]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
Merge branch 'master' of git+ssh://jneem@git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / lily / slur.cc
1 /*
2   slur.cc -- implement external interface for Slur
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
7   Jan Nieuwenhuizen <janneke@gnu.org>
8 */
9
10 #include "slur.hh"
11 #include "grob-info.hh"
12 #include "grob-array.hh"
13 #include "beam.hh"
14 #include "bezier.hh"
15 #include "directional-element-interface.hh"
16 #include "font-interface.hh"
17 #include "pointer-group-interface.hh"
18 #include "lookup.hh"
19 #include "main.hh"              // DEBUG_SLUR_SCORING
20 #include "note-column.hh"
21 #include "output-def.hh"
22 #include "spanner.hh"
23 #include "staff-symbol-referencer.hh"
24 #include "staff-symbol.hh"
25 #include "stem.hh"
26 #include "text-interface.hh"
27 #include "tie.hh"
28 #include "warn.hh"
29 #include "slur-scoring.hh"
30 #include "separation-item.hh"
31 #include "script-interface.hh"
32
33
34
35 MAKE_SCHEME_CALLBACK(Slur, calc_direction, 1)
36 SCM
37 Slur::calc_direction (SCM smob)
38 {
39   Grob *me = unsmob_grob (smob);
40   extract_grob_set (me, "note-columns", encompasses);
41
42   if (encompasses.empty ())
43     {
44       me->suicide ();
45       return SCM_BOOL_F;
46     }
47
48   Direction d = DOWN;
49   for (vsize i = 0; i < encompasses.size (); i++)
50     {
51       if (Note_column::dir (encompasses[i]) < 0)
52         {
53           d = UP;
54           break;
55         }
56     }
57   return scm_from_int (d);
58 }
59
60 MAKE_SCHEME_CALLBACK (Slur, pure_height, 3);
61 SCM
62 Slur::pure_height (SCM smob, SCM start_scm, SCM end_scm)
63 {
64   Grob *me = unsmob_grob (smob);
65   int start = scm_to_int (start_scm);
66   int end = scm_to_int (end_scm);
67   Real height = robust_scm2double (me->get_property ("height-limit"), 2.0);
68
69   extract_grob_set (me, "note-columns", encompasses);
70   Interval ret;
71
72   Grob *parent = me->get_parent (Y_AXIS);
73   if (common_refpoint_of_array (encompasses, me, Y_AXIS) != parent)
74     /* this could happen if, for example, we are a cross-staff slur.
75        in this case, we want to be ignored */
76     return ly_interval2scm (Interval ());
77
78   for (vsize i = 0; i < encompasses.size (); i++)
79     {
80       Interval d = encompasses[i]->pure_height (parent, start, end);
81       if (!d.is_empty ())
82         ret.unite (d);
83     }
84
85   ret.widen (height * 0.5);
86   return ly_interval2scm (ret);
87 }
88
89 MAKE_SCHEME_CALLBACK (Slur, height, 1);
90 SCM
91 Slur::height (SCM smob)
92 {
93   Grob *me = unsmob_grob (smob);
94
95   // FIXME uncached
96   Stencil *m = me->get_stencil ();
97   return m ? ly_interval2scm (m->extent (Y_AXIS))
98     : ly_interval2scm (Interval ());
99 }
100
101 /*
102   Ugh should have dash-length + dash-period
103 */
104 MAKE_SCHEME_CALLBACK (Slur, print, 1);
105 SCM
106 Slur::print (SCM smob)
107 {
108   Grob *me = unsmob_grob (smob);
109   extract_grob_set (me, "note-columns", encompasses);
110   if (encompasses.empty ())
111     {
112       me->suicide ();
113       return SCM_EOL;
114     }
115
116   Real staff_thick = Staff_symbol_referencer::line_thickness (me);
117   Real base_thick = staff_thick
118     * robust_scm2double (me->get_property ("thickness"), 1);
119   Real line_thick = staff_thick
120     * robust_scm2double (me->get_property ("line-thickness"), 1);
121
122   Bezier one = get_curve (me);
123   Stencil a;
124
125   /*
126     TODO: replace dashed with generic property.
127   */
128   SCM p = me->get_property ("dash-period");
129   SCM f = me->get_property ("dash-fraction");
130   if (scm_is_number (p) && scm_is_number (f))
131     a = Lookup::dashed_slur (one, line_thick, robust_scm2double (p, 1.0),
132                              robust_scm2double (f, 0));
133   else
134     a = Lookup::slur (one,
135                       get_grob_direction (me) * base_thick,
136                       line_thick);
137
138 #if DEBUG_SLUR_SCORING
139   SCM quant_score = me->get_property ("quant-score");
140
141   if (to_boolean (me->layout ()
142                   ->lookup_variable (ly_symbol2scm ("debug-slur-scoring")))
143       && scm_is_string (quant_score))
144     {
145       string str;
146       SCM properties = Font_interface::text_font_alist_chain (me);
147
148
149       if (!scm_is_number (me->get_property ("font-size")))
150         properties = scm_cons (scm_acons (ly_symbol2scm ("font-size"), scm_from_int (-6), SCM_EOL),
151                              properties);
152       
153       Stencil tm = *unsmob_stencil (Text_interface::interpret_markup
154                                     (me->layout ()->self_scm (), properties,
155                                      quant_score));
156       a.add_at_edge (Y_AXIS, get_grob_direction (me), tm, 1.0);
157     }
158 #endif
159
160   return a.smobbed_copy ();
161 }
162
163
164 /*
165   it would be better to do this at engraver level, but that is
166   fragile, as the breakabl items are generated on staff level, at
167   which point slur starts and ends have to be tracked
168 */
169 void
170 Slur::replace_breakable_encompass_objects (Grob *me)
171 {
172   extract_grob_set (me, "encompass-objects", extra_objects);
173   vector<Grob *> new_encompasses;
174
175   for (vsize i = 0; i < extra_objects.size (); i++)
176     {
177       Grob *g = extra_objects[i];
178       
179       if (Separation_item::has_interface (g))
180         {
181           extract_grob_set (g, "elements", breakables);
182           for (vsize j = 0; j < breakables.size (); j++)
183             if (breakables[j]->get_property ("avoid-slur") == ly_symbol2scm ("inside"))
184               new_encompasses.push_back (breakables[j]);
185         }
186       else
187         new_encompasses.push_back (g);
188     }
189
190   SCM encompass_scm = me->get_object ("encompass-objects");
191   if (Grob_array::unsmob (encompass_scm))
192     {
193       vector<Grob *> &arr =
194         unsmob_grob_array (encompass_scm)->array_reference ();
195       arr = new_encompasses;
196     }
197 }
198
199 Bezier
200 Slur::get_curve (Grob *me)
201 {
202   Bezier b;
203   int i = 0;
204   for (SCM s = me->get_property ("control-points"); scm_is_pair (s);
205        s = scm_cdr (s))
206     b.control_[i++] = ly_scm2offset (scm_car (s));
207
208   return b;
209 }
210
211 void
212 Slur::add_column (Grob *me, Grob *n)
213 {
214   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
215   add_bound_item (dynamic_cast<Spanner *> (me), n);
216 }
217
218 void
219 Slur::add_extra_encompass (Grob *me, Grob *n)
220 {
221   Pointer_group_interface::add_grob (me, ly_symbol2scm ("encompass-objects"), n);
222 }
223
224 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, pure_outside_slur_callback, 4, 1, "");
225 SCM
226 Slur::pure_outside_slur_callback (SCM grob, SCM start_scm, SCM end_scm, SCM offset_scm)
227 {
228   int start = robust_scm2int (start_scm, 0);
229   int end = robust_scm2int (end_scm, 0);
230   Grob *script = unsmob_grob (grob);
231   Grob *slur = unsmob_grob (script->get_object ("slur"));
232   if (!slur)
233     return offset_scm;
234
235   SCM avoid = script->get_property ("avoid-slur");
236   if (avoid != ly_symbol2scm ("outside") && avoid != ly_symbol2scm ("around"))
237     return offset_scm;
238
239   Real offset = robust_scm2double (offset_scm, 0.0);
240   Direction dir = get_grob_direction (script);
241   return scm_from_double (offset + dir * slur->pure_height (slur, start, end).length () / 4);
242 }
243
244 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Slur, outside_slur_callback, 2, 1, "");
245 SCM
246 Slur::outside_slur_callback (SCM grob, SCM offset_scm)
247 {
248   Grob *script = unsmob_grob (grob);
249   Grob *slur = unsmob_grob (script->get_object ("slur")); 
250
251   if (!slur)
252     return offset_scm;
253
254   SCM avoid = script->get_property ("avoid-slur");
255   if (avoid != ly_symbol2scm ("outside")
256       && avoid != ly_symbol2scm ("around"))
257     return offset_scm;
258   
259   Direction dir = get_grob_direction (script);
260   if (dir == CENTER)
261     return offset_scm;
262
263   Grob *cx = script->common_refpoint (slur, X_AXIS);
264   Grob *cy = script->common_refpoint (slur, Y_AXIS);
265
266   Bezier curve = Slur::get_curve (slur);
267
268   curve.translate (Offset (slur->relative_coordinate (cx, X_AXIS),
269                            slur->relative_coordinate (cy, Y_AXIS)));
270
271   Interval yext = robust_relative_extent (script, cy, Y_AXIS);
272   Interval xext = robust_relative_extent (script, cx, X_AXIS);
273
274   Real offset = robust_scm2double (offset_scm, 0);
275   yext.translate (offset);
276   
277   /* FIXME: slur property, script property?  */
278   Real slur_padding = robust_scm2double (script->get_property ("slur-padding"),
279                                          0.0);
280   yext.widen (slur_padding);
281
282   Real EPS = 1e-3;
283   Interval bezext (curve.control_[0][X_AXIS], curve.control_[3][X_AXIS]);
284   bool consider[] = { false, false, false };
285   Real ys[] = {0, 0, 0};
286   bool do_shift = false;
287   
288   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
289     {
290       Real x = xext.linear_combination ((Direction) d);
291       consider[k] = bezext.contains (x);
292
293       if (consider[k])
294         {
295           ys[k]
296             = (fabs (bezext[LEFT] - x) < EPS)
297             ? curve.control_[0][Y_AXIS]
298             : ((fabs (bezext[RIGHT] - x) < EPS)
299                ? curve.control_[3][Y_AXIS]
300                : curve.get_other_coordinate (X_AXIS, x));
301
302           /* Request shift if slur is contained script's Y, or if
303              script is inside slur and avoid == outside.  */
304           if (yext.contains (ys[k])
305               || (dir * ys[k] > dir * yext[-dir] && avoid == ly_symbol2scm ("outside")))
306             do_shift = true;
307         }
308     }
309
310   Real avoidance_offset = 0.0;
311   for (int d = LEFT, k = 0; d <= RIGHT; d++, k++)
312     if (consider[k]) 
313       avoidance_offset = dir * (max (dir * avoidance_offset,
314                                      dir * (ys[k] - yext[-dir] + dir * slur_padding)));
315   
316   return scm_from_double (offset + avoidance_offset);
317 }
318
319 /*
320  * Used by Slur_engraver:: and Phrasing_slur_engraver::
321  */
322 void
323 Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
324                                           vector<Grob*> &slurs,
325                                           vector<Grob*> &end_slurs)
326 {
327   if (slurs.empty () && end_slurs.empty ())
328     return;
329   
330   Grob *e = info.grob ();
331   SCM avoid = e->get_property ("avoid-slur");
332   if (Tie::has_interface (e)
333       || avoid == ly_symbol2scm ("inside"))
334     {
335       for (vsize i = slurs.size (); i--;)
336         add_extra_encompass (slurs[i], e);
337       for (vsize i = end_slurs.size (); i--;)
338         add_extra_encompass (end_slurs[i], e);
339     }
340   else if (avoid == ly_symbol2scm ("outside")
341            || avoid == ly_symbol2scm ("around"))
342     {
343       Grob *slur;
344       if (end_slurs.size () && !slurs.size ())
345         slur = end_slurs[0];
346       else
347         slur = slurs[0];
348
349       if (slur)
350         {
351           chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
352           e->set_object ("slur", slur->self_scm ());
353         }
354     }
355   else
356     e->warning ("Ignoring grob for slur. avoid-slur not set?");
357 }
358
359 MAKE_SCHEME_CALLBACK (Slur, cross_staff, 1)
360 SCM
361 Slur::cross_staff (SCM smob)
362 {
363   Grob *me = unsmob_grob (smob);
364   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
365   assert (staff); // delete me
366   extract_grob_set (me, "note-columns", cols);
367
368   for (vsize i = 0; i < cols.size (); i++)
369     if (Staff_symbol_referencer::get_staff_symbol (cols[i]) != staff)
370       return SCM_BOOL_T;
371   return SCM_BOOL_F;
372 }
373
374 ADD_INTERFACE (Slur,
375                
376                "A slur",
377                
378                /* properties */
379                "avoid-slur "    /* UGH. */
380                "control-points "
381                "dash-fraction "
382                "dash-period "
383                "details "
384                "direction "
385                "eccentricity "
386                "encompass-objects "
387                "height-limit "
388                "inspect-quants "
389                "inspect-index "
390                "line-thickness "
391                "note-columns "
392                "positions "
393                "quant-score "
394                "ratio "
395                "thickness "
396                );
397