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