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