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