]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
release: 1.3.79
[lilypond.git] / lily / slur.cc
1 /*
2   slur.cc -- implement  Slur
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1996--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7     Jan Nieuwenhuizen <janneke@gnu.org>
8 */
9
10 /*
11   [TODO]
12     * fix broken interstaff slurs
13     * begin and end should be treated as a/acknowledge Scripts.
14     * broken slur should have uniform trend
15     * smart changing of endings
16     * smart changing of (Y-?)offsets to avoid ugly beziers
17        (along-side-stem)
18  */
19
20 #include "directional-element-interface.hh"
21 #include "group-interface.hh"
22 #include "slur.hh"
23 #include "lookup.hh"
24 #include "paper-def.hh"
25 #include "note-column.hh"
26 #include "stem.hh"
27 #include "paper-column.hh"
28 #include "molecule.hh"
29 #include "debug.hh"
30 #include "slur-bezier-bow.hh"
31 #include "main.hh"
32 #include "group-interface.hh"
33 #include "staff-symbol-referencer.hh"
34 #include "spanner.hh"
35
36
37 void
38 Slur::set_interface (Score_element*me)
39 {
40   me->set_elt_property ("attachment", gh_cons (SCM_BOOL_F, SCM_BOOL_F));
41   me->set_interface (ly_symbol2scm ("slur-interface"));
42 }
43
44 void
45 Slur::add_column (Score_element*me, Score_element*n)
46 {
47   if (!gh_pair_p (n->get_elt_property ("note-heads")))
48     warning (_ ("Putting slur over rest.  Ignoring."));
49   else
50     {
51       Pointer_group_interface (me, "note-columns").add_element (n);
52       me->add_dependency (n);
53     }
54
55   add_bound_item (dynamic_cast<Spanner*> (me), dynamic_cast<Item*>(n));
56 }
57
58 void
59 Slur::de_uglyfy (Score_element*me, Slur_bezier_bow* bb, Real default_height)
60 {
61   Real length = bb->curve_.control_[3][X_AXIS] ; 
62   Real ff = bb->fit_factor ();
63   for (int i = 1; i < 3; i++)
64     {
65       Real ind = abs (bb->curve_.control_[(i-1)*3][X_AXIS]
66                       - bb->curve_.control_[i][X_AXIS]) / length;
67       Real h = bb->curve_.control_[i][Y_AXIS] * ff / length;
68
69       Real f = default_height / length;
70       Real c1 = me->paper_l ()->get_var ("bezier_control1");
71       Real c2 = me->paper_l ()->get_var ("bezier_control2");
72       Real c3 = me->paper_l ()->get_var ("bezier_control3");
73       if (h > c1 * f)
74         {
75           h = c1 * f; 
76         }
77       else if (h > c2 + c3 * ind)
78         {
79           h = c2 + c3 * ind; 
80         }
81       
82       bb->curve_.control_[i][Y_AXIS] = h * length;
83     } 
84
85   bb->curve_.assert_sanity ();
86 }
87
88 Direction
89 Slur::get_default_dir (Score_element*me) 
90 {
91   Link_array<Score_element> encompass_arr =
92     Pointer_group_interface__extract_elements (me, (Score_element*)0, "note-columns");
93   
94   Direction d = DOWN;
95   for (int i=0; i < encompass_arr.size (); i ++) 
96     {
97       if (Note_column::dir (encompass_arr[i]) < 0) 
98         {
99           d = UP;
100           break;
101         }
102     }
103   return d;
104 }
105
106
107
108
109
110 Offset
111 Slur::encompass_offset (Score_element*me,
112                         Score_element* col,
113                         Score_element **common) 
114 {
115   Offset o;
116   Score_element* stem_l = unsmob_element (col->get_elt_property ("stem"));
117   
118   Direction dir = Directional_element_interface (me).get ();
119   
120   if (!stem_l)
121     {
122       warning (_ ("Slur over rest?"));
123       o[X_AXIS] = col->relative_coordinate (common[X_AXIS], X_AXIS);
124       o[Y_AXIS] = col->relative_coordinate (common[Y_AXIS], Y_AXIS);
125       return o;  
126     }
127   Direction stem_dir = Directional_element_interface (stem_l).get ();
128   o[X_AXIS] = stem_l->relative_coordinate (0, X_AXIS);
129
130   /*
131     Simply set x to middle of notehead
132    */
133
134   o[X_AXIS] -= 0.5 * stem_dir * col->extent (X_AXIS).length ();
135
136   if ((stem_dir == dir)
137       && !stem_l->extent (Y_AXIS).empty_b ())
138     {
139       o[Y_AXIS] = stem_l->relative_coordinate (common[Y_AXIS], Y_AXIS); // iuhg
140     }
141   else
142     {
143       o[Y_AXIS] = col->relative_coordinate (common[Y_AXIS], Y_AXIS);    // ugh
144     }
145
146   /*
147    leave a gap: slur mustn't touch head/stem
148    */
149   o[Y_AXIS] += dir * me->paper_l ()->get_var ("slur_y_free");
150   return o;
151 }
152
153 MAKE_SCHEME_CALLBACK(Slur,after_line_breaking);
154
155 SCM
156 Slur::after_line_breaking (SCM smob)
157 {
158   Score_element *me = unsmob_element (smob);
159   set_extremities (me);
160   set_control_points (me);
161   return SCM_UNSPECIFIED;
162
163
164 void
165 Slur::set_extremities (Score_element*me)
166 {
167   if (!Directional_element_interface (me).get ())
168     Directional_element_interface (me).set (get_default_dir (me));
169
170   Direction dir = LEFT;
171   do 
172     {
173       if (!gh_symbol_p (index_cell (me->get_elt_property ("attachment"), dir)))
174         {
175           
176           // for (SCM s = get_elt_property ("slur-extremity-rules"); s != SCM_EOL; s = gh_cdr (s))
177           for (SCM s = scm_eval (ly_symbol2scm ("slur-extremity-rules"));
178                s != SCM_EOL; s = gh_cdr (s))
179             {
180               SCM r = gh_call2 (gh_caar (s), me->self_scm (),
181                                  gh_int2scm ((int)dir));
182               if (r != SCM_BOOL_F)
183                 {
184                   index_set_cell (me->get_elt_property ("attachment"), dir,
185                                   gh_cdar (s));
186                   break;
187                 }
188             }
189         }
190     }
191   while (flip (&dir) != LEFT);
192 }
193
194 Offset
195 Slur::get_attachment (Score_element*me,Direction dir,
196                       Score_element **common) 
197 {
198   Spanner*sp = dynamic_cast<Spanner*>(me);
199   SCM s = me->get_elt_property ("attachment");
200   SCM a = dir == LEFT ? gh_car (s) : gh_cdr (s);
201   String str = ly_symbol2string (a);
202   Real ss = Staff_symbol_referencer::staff_space ((Score_element*)me);
203   Real hs = ss / 2.0;
204   Offset o;
205
206
207   if (Note_column::has_interface (sp->get_bound (dir)))
208     {
209       Score_element * n =sp->get_bound (dir);
210       if (Score_element*st = Note_column::stem_l (n))
211         {
212
213           if (str == "head")
214             {
215               o = Offset (0, Stem::chord_start_f (st ));
216               /*
217                 Default position is centered in X, on outer side of head Y
218                */
219               o += Offset (0.5 * n->extent (X_AXIS).length (),
220                            0.5 * ss * Directional_element_interface (me).get ());
221             }
222           else if (str == "alongside-stem")
223             {
224               o = Offset (0, Stem::chord_start_f (st ));
225               /*
226                 Default position is on stem X, on outer side of head Y
227                */
228               o += Offset (n->extent (X_AXIS).length ()
229                            * (1 + Stem::get_direction (st )),
230                            0.5 * ss * Directional_element_interface (me).get ());
231             }
232           else if (str == "stem")
233             {
234               o = Offset (0, Stem::stem_end_position (st ) * hs);
235               /*
236                 Default position is on stem X, at stem end Y
237                */
238               o += Offset (0.5 *
239                            (n->extent (X_AXIS).length ()
240                             - st->extent (X_AXIS).length ())
241                             * (1 + Stem::get_direction (st )),
242                             0);
243             }
244           else if (str == "loose-end")
245             {
246               SCM other_a = dir == LEFT ? gh_cdr (s) : gh_car (s);
247               if (ly_symbol2string (other_a) != "loose-end")
248                 {
249                   o = Offset (0, get_attachment (me, -dir, common)[Y_AXIS]);
250                 }
251             }
252
253           
254           SCM l = scm_assoc
255             (scm_listify (a,
256                           gh_int2scm (Stem::get_direction (st ) * dir),
257                           gh_int2scm (Directional_element_interface (me).get () * dir),
258                           SCM_UNDEFINED),
259              scm_eval (ly_symbol2scm ("slur-extremity-offset-alist")));
260           
261           if (l != SCM_BOOL_F)
262             {
263               o += ly_scm2offset (gh_cdr (l)) * ss * dir;
264             }
265         }
266     }
267
268
269   /*
270     What if get_bound () is not a note-column?
271    */
272   if (str != "loose-end"
273       && sp->get_bound (dir)->common_refpoint (common[Y_AXIS], Y_AXIS) == common[Y_AXIS])
274     {      
275       o[Y_AXIS] += sp->get_bound (dir)->relative_coordinate (common[Y_AXIS], Y_AXIS) 
276         - me->relative_coordinate (common[Y_AXIS], Y_AXIS);
277     }
278   return o;
279 }
280
281 Array<Offset>
282 Slur::get_encompass_offset_arr (Score_element*me) 
283 {
284     Spanner*sp = dynamic_cast<Spanner*>(me);
285   SCM eltlist = me->get_elt_property ("note-columns");
286   Score_element *common[] = {me->common_refpoint (eltlist,X_AXIS),
287                              me->common_refpoint (eltlist,Y_AXIS)};
288
289
290   common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
291   common[X_AXIS] = common[X_AXIS]->common_refpoint (sp->get_bound (LEFT), X_AXIS);
292   
293   Link_array<Score_element>  encompass_arr;
294   while (gh_pair_p (eltlist))
295     {
296       encompass_arr.push (unsmob_element (gh_car (eltlist)));      
297       eltlist =gh_cdr (eltlist);
298     }
299   encompass_arr.reverse ();
300
301   
302   Array<Offset> offset_arr;
303
304   Offset origin (me->relative_coordinate (common[X_AXIS], X_AXIS),
305                  me->relative_coordinate (common[Y_AXIS], Y_AXIS)); 
306
307   int first = 1;
308   int last = encompass_arr.size () - 2;
309
310   offset_arr.push (get_attachment (me, LEFT, common));
311
312   /*
313     left is broken edge
314   */
315
316   if (encompass_arr[0] != sp->get_bound (LEFT))
317     {
318       first--;
319
320       // ?
321       offset_arr[0][Y_AXIS] -=
322         encompass_arr[0]->relative_coordinate (common[Y_AXIS], Y_AXIS) 
323         - me->relative_coordinate (common[Y_AXIS], Y_AXIS); 
324     }
325
326   /*
327     right is broken edge
328   */
329   if (encompass_arr.top () != sp->get_bound (RIGHT))
330     {
331       last++;
332     }
333
334   for (int i = first; i <= last; i++)
335     {
336       Offset o (encompass_offset (me, encompass_arr[i], common));
337       offset_arr.push (o - origin);
338     }
339
340   offset_arr.push (Offset (sp->spanner_length (), 0) + get_attachment (me, RIGHT,common));
341
342   if (encompass_arr[0] != sp->get_bound (LEFT))
343     {
344       offset_arr.top ()[Y_AXIS] -= encompass_arr.top ()->relative_coordinate (common[Y_AXIS], Y_AXIS) 
345         - me->relative_coordinate (common[Y_AXIS], Y_AXIS);
346     }
347
348   return offset_arr;
349 }
350
351
352 MAKE_SCHEME_CALLBACK(Slur,set_spacing_rods);
353 SCM
354 Slur::set_spacing_rods (SCM smob)
355 {
356   Score_element*me = unsmob_element (smob);
357
358   Rod r;
359   Spanner*sp = dynamic_cast<Spanner*>(me);
360   r.item_l_drul_[LEFT] = sp->get_bound (LEFT);
361   r.item_l_drul_[RIGHT] = sp->get_bound (RIGHT);
362   r.distance_f_ =
363     gh_scm2double (me->get_elt_property ("minimum-length"))
364     * me->paper_l ()->get_var ("staffspace");
365
366   r.add_to_cols ();
367   return SCM_UNSPECIFIED;
368 }
369
370
371 /*
372   Ugh should have dash-length + dash-period
373  */
374 MAKE_SCHEME_CALLBACK(Slur,brew_molecule);
375 SCM
376 Slur::brew_molecule (SCM smob)
377 {
378   Score_element * me = unsmob_element (smob);
379   Real thick = me->paper_l ()->get_var ("stafflinethickness") *
380     gh_scm2double (me->get_elt_property ("thickness"));
381   Bezier one = get_curve (me);
382
383   Molecule a;
384   SCM d =  me->get_elt_property ("dashed");
385   if (gh_number_p (d))
386     a = me->lookup_l ()->dashed_slur (one, thick, thick * gh_scm2double (d));
387   else
388     a = me->lookup_l ()->slur (one, Directional_element_interface (me).get () * thick, thick);
389
390   return a.create_scheme();
391 }
392
393 void
394 Slur::set_control_points (Score_element*me)
395 {
396   Real staff_space = Staff_symbol_referencer::staff_space ((Score_element*)me);  
397   Real h_inf = me->paper_l ()->get_var ("slur_height_limit_factor") *
398     staff_space;
399   Real r_0 = me->paper_l ()->get_var ("slur_ratio");
400   
401   Slur_bezier_bow bb (get_encompass_offset_arr (me),
402                       Directional_element_interface (me).get (),
403                       h_inf, r_0);
404
405   if (bb.fit_factor () > 1.0)
406     {
407       Real length = bb.curve_.control_[3][X_AXIS]; 
408       Real default_height = slur_height (length, h_inf, r_0);
409       bb.minimise_enclosed_area (me->paper_l());
410       
411       Real bff = me->paper_l ()->get_var ("slur_force_blowfit");
412       bb.curve_.control_[1][Y_AXIS] *= bff;
413       bb.curve_.control_[2][Y_AXIS] *= bff;
414       bb.blow_fit ();
415
416       Real sb = me->paper_l ()->get_var ("slur_beautiful");
417       Real beautiful = length * default_height * sb;
418       Real area = bb.enclosed_area_f ();
419       
420       /*
421         Slurs that fit beautifully are not ugly
422       */
423       if (area > beautiful)
424         de_uglyfy (me, &bb, default_height);
425     }
426
427   Bezier b = bb.get_bezier ();
428
429
430   SCM controls = SCM_EOL;
431   for (int i= 4; i--;)
432     controls = gh_cons ( ly_offset2scm (b.control_[i]), controls);
433
434   me->set_elt_property ("control-points", controls);
435 }
436   
437   
438 Bezier
439 Slur::get_curve (Score_element*me) 
440 {
441   Bezier b;
442   int i = 0;
443
444   if (!Directional_element_interface (me).get ()
445       || ! gh_symbol_p (index_cell (me->get_elt_property ("attachment"), LEFT)))
446     set_extremities (me);
447   
448   if (!gh_pair_p (me->get_elt_property ("control-points")))
449     set_control_points (me);
450   
451   
452   for (SCM s= me->get_elt_property ("control-points"); s != SCM_EOL; s = gh_cdr (s))
453     {
454       b.control_[i] = ly_scm2offset (gh_car (s));
455       i++;
456     }
457   
458   Array<Offset> enc (get_encompass_offset_arr (me));
459   Direction dir = Directional_element_interface (me).get ();
460   
461   Real x1 = enc[0][X_AXIS];
462   Real x2 = enc.top ()[X_AXIS];
463   
464   Real off = 0.0;
465   for (int i=1; i < enc.size ()-1; i++)
466     {
467       Real x = enc[i][X_AXIS];
468       if (x > x1 && x <x2)
469         {
470           Real y = b.get_other_coordinate (X_AXIS, x);
471           off = off >? dir *  (enc[i][Y_AXIS] - y);
472         }
473     }
474   b.translate (Offset (0, dir * off));
475   return b;
476 }
477
478
479 bool
480 Slur::has_interface (Score_element*me)
481 {
482   return me->has_interface (ly_symbol2scm ("slur-interface"));
483 }
484
485