]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
release: 1.3.19
[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,  1997--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  */
15
16 #include "directional-element-interface.hh"
17 #include "group-interface.hh"
18 #include "slur.hh"
19 #include "lookup.hh"
20 #include "paper-def.hh"
21 #include "note-column.hh"
22 #include "stem.hh"
23 #include "paper-column.hh"
24 #include "molecule.hh"
25 #include "debug.hh"
26 #include "box.hh"
27 #include "bezier.hh"
28 #include "bezier-bow.hh"
29 #include "main.hh"
30 #include "cross-staff.hh"
31 #include "group-interface.hh"
32
33 Slur::Slur ()
34 {
35   dy_f_drul_[LEFT] = dy_f_drul_[RIGHT] = 0.0;
36   dx_f_drul_[LEFT] = dx_f_drul_[RIGHT] = 0.0;
37   set_elt_property ("note-columns", SCM_EOL);
38 }
39
40 void
41 Slur::add_column (Note_column*n)
42 {
43   if (!gh_pair_p (n->get_elt_property ("note-heads")))
44     warning (_ ("Putting slur over rest.  Ignoring."));
45   else
46     {
47       Group_interface gi (this, "note-columns");
48       gi.add_element (n);
49       add_dependency (n);
50     }
51 }
52
53 Direction
54 Slur::get_default_dir () const
55 {
56   Link_array<Note_column> encompass_arr =
57     Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
58   
59   Direction d = DOWN;
60   for (int i=0; i < encompass_arr.size (); i ++) 
61     {
62       if (encompass_arr[i]->dir () < 0) 
63         {
64           d = UP;
65           break;
66         }
67     }
68   return d;
69 }
70
71 void
72 Slur::do_add_processing ()
73 {
74   Link_array<Note_column> encompass_arr =
75     Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
76   set_bounds (LEFT, encompass_arr[0]);    
77   if (encompass_arr.size () > 1)
78     set_bounds (RIGHT, encompass_arr.top ());
79 }
80
81
82
83 Offset
84 Slur::encompass_offset (Note_column const* col) const
85 {
86   Offset o;
87   Stem* stem_l = col->stem_l ();
88   Direction dir = directional_element (this).get ();
89   
90   if (!stem_l)
91     {
92       warning (_ ("Slur over rest?"));
93       o[X_AXIS] = col->hpos_f ();
94       o[Y_AXIS] = col->extent (Y_AXIS)[dir];
95       return o;  
96     }
97   Direction stem_dir = directional_element (stem_l).get ();
98   o[X_AXIS] = stem_l->hpos_f ();
99
100   /*
101     Simply set x to middle of notehead
102    */
103
104   o[X_AXIS] -= 0.5 * stem_dir * col->extent (X_AXIS).length ();
105
106   if ((stem_dir == dir)
107       && !stem_l->extent (Y_AXIS).empty_b ())
108     {
109       o[Y_AXIS] = stem_l->extent (Y_AXIS)[dir];
110     }
111   else
112     {
113       o[Y_AXIS] = col->extent (Y_AXIS)[dir];
114     }
115
116   /*
117    leave a gap: slur mustn't touch head/stem
118    */
119   o[Y_AXIS] += dir * paper_l ()->get_var ("slur_y_free");
120   o[Y_AXIS] -= calc_interstaff_dist (stem_l, this);
121   return o;
122 }
123
124 /*
125   ARGRARGRARGRARGAR!
126
127   Fixme
128  */
129 void
130 Slur::do_post_processing ()
131 {
132   Link_array<Note_column> encompass_arr =
133     Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
134
135   if (!encompass_arr.size ())
136     {
137       set_elt_property ("transparent", SCM_BOOL_T);
138       set_empty (X_AXIS);
139       set_empty (Y_AXIS);
140       return;
141     }
142
143   if (!directional_element (this).get ())
144     directional_element (this).set (get_default_dir ());
145
146   /* 
147    Slur and tie placement [OSU]
148
149    Slurs:
150    * x = centre of head - d * x_gap_f
151
152    TODO:
153    * y = length < 5ss : horizontal tangent + d * 0.25 ss
154      y = length >= 5ss : y next interline - d * 0.25 ss
155    */
156
157   Real staff_space = paper_l ()->get_var ("interline");
158   Real half_staff_space = staff_space / 2;
159
160   Real x_gap_f = paper_l ()->get_var ("slur_x_gap");
161   Real y_gap_f = paper_l ()->get_var ("slur_y_gap");
162
163   Drul_array<Note_column*> note_column_drul;
164   note_column_drul[LEFT] = encompass_arr[0];
165   note_column_drul[RIGHT] = encompass_arr.top ();
166
167   bool fix_broken_b = false;
168
169   Direction my_dir = directional_element (this).get ();
170   
171   Direction d = LEFT;
172   do 
173     {
174       dx_f_drul_[d] = 0;
175       dy_f_drul_[d] = 0;
176       
177       if ((note_column_drul[d] == spanned_drul_[d])
178           && note_column_drul[d]->first_head ()
179           && (note_column_drul[d]->stem_l ()))
180         {
181           Stem* stem_l = note_column_drul[d]->stem_l ();
182           /*
183             side directly attached to note head;
184             no beam getting in the way
185           */
186           if ((stem_l->extent (Y_AXIS).empty_b ()
187                || !((stem_l->get_direction () == my_dir) && (my_dir != d)))
188               && !((my_dir == stem_l->get_direction ())
189                    && stem_l->beam_l () && (stem_l->beam_count (-d) >= 1)))
190             {
191               dx_f_drul_[d] = spanned_drul_[d]->extent (X_AXIS).length () / 2;
192               dx_f_drul_[d] -= d * x_gap_f;
193
194               if (stem_l->get_direction () != my_dir)
195                 {
196                   dy_f_drul_[d] = note_column_drul[d]->extent (Y_AXIS)[my_dir];
197                 }
198               else
199                 {
200                   dy_f_drul_[d] = stem_l->chord_start_f ()
201                     + my_dir * half_staff_space;
202                 }
203               dy_f_drul_[d] += my_dir * y_gap_f;
204             }
205           /*
206             side attached to (visible) stem
207           */
208           else
209             {
210               dx_f_drul_[d] = stem_l->hpos_f ()
211                 - spanned_drul_[d]->relative_coordinate (0, X_AXIS);
212               /*
213                 side attached to beamed stem
214                */
215               if (stem_l->beam_l () && (stem_l->beam_count (-d) >= 1))
216                 {
217                   dy_f_drul_[d] = stem_l->extent (Y_AXIS)[my_dir];
218                   dy_f_drul_[d] += my_dir * 2 * y_gap_f;
219                 }
220               /*
221                 side attached to notehead, with stem getting in the way
222                */
223               else
224                 {
225                   dx_f_drul_[d] -= d * x_gap_f;
226                   
227                   dy_f_drul_[d] = stem_l->chord_start_f ()
228                     + my_dir * half_staff_space;
229                   dy_f_drul_[d] += my_dir * y_gap_f;
230                 }
231             }
232         }
233       /*
234         loose end
235       */
236       else
237         {
238           dx_f_drul_[d] = get_broken_left_end_align ();
239                 
240           /*
241             broken: should get y from other piece, so that slur
242             continues up/down trend
243
244             for now: be horizontal..
245           */
246           fix_broken_b = true;
247         }
248     }
249   while (flip (&d) != LEFT);
250
251   int cross_count =  cross_staff_count ();
252   bool interstaff_b = (0 < cross_count) && (cross_count < encompass_arr.size ());
253
254   Drul_array<Offset> info_drul;
255   Drul_array<Real> interstaff_interval;
256
257   do
258     {
259       info_drul[d] = encompass_offset (encompass_arr.boundary (d, 0));
260       interstaff_interval[d] = - calc_interstaff_dist (encompass_arr.boundary (d,0),
261                                                      this);
262     }
263   while (flip (&d) != LEFT);
264   
265   Real interstaff_f = interstaff_interval[RIGHT] - interstaff_interval[LEFT];
266
267   if (fix_broken_b)
268     {
269       Direction d = (encompass_arr.top () != spanned_drul_[RIGHT]) ?
270         RIGHT : LEFT;
271       dy_f_drul_[d] = info_drul[d][Y_AXIS];
272       if (!interstaff_b)
273         {
274           dy_f_drul_[d] -= interstaff_interval[d];
275           if (cross_count)      // interstaff_i  ? 
276             {
277               dy_f_drul_[LEFT] += interstaff_interval[d];
278               dy_f_drul_[RIGHT] += interstaff_interval[d];
279             }
280         }
281     }
282         
283
284   /*
285     Now we've got a fine slur
286     Catch and correct some ugly cases
287    */
288   String infix = interstaff_b ? "interstaff_" : "";
289   Real height_damp_f = paper_l ()->get_var ("slur_"+infix +"height_damping");
290   Real slope_damp_f = paper_l ()->get_var ("slur_"+infix +"slope_damping");
291   Real snap_f = paper_l ()->get_var ("slur_"+infix +"snap_to_stem");
292   Real snap_max_dy_f = paper_l ()->get_var ("slur_"+infix +"snap_max_slope_change");
293
294   if (!fix_broken_b)
295     dy_f_drul_[RIGHT] += interstaff_f;
296
297   Real dy_f = dy_f_drul_[RIGHT] - dy_f_drul_[LEFT];
298   if (!fix_broken_b)
299     dy_f -= interstaff_f;
300   Real dx_f = spanner_length ()+ dx_f_drul_[RIGHT] - dx_f_drul_[LEFT];
301
302   /*
303     Avoid too steep slurs.
304    */
305   Real slope_ratio_f = abs (dy_f / dx_f);
306   if (slope_ratio_f > slope_damp_f)
307     {
308       Direction d = (Direction)(- my_dir * (sign (dy_f)));
309       if (!d)
310         d = LEFT;
311       Real damp_f = (slope_ratio_f - slope_damp_f) * dx_f;
312       /*
313         must never change sign of dy
314        */
315       damp_f = damp_f <? abs (dy_f);
316       dy_f_drul_[d] += my_dir * damp_f;
317     }
318
319   /*
320    Avoid too high slurs 
321
322    Wierd slurs may look a lot better after they have been
323    adjusted a bit.
324    So, we'll do this in 3 steps
325    */
326   for (int i = 0; i < 3; i++)
327     {
328       Bezier c (get_curve ());
329       
330
331       Real height_f = c.extent (X_AXIS).length ();
332       Real width_f = c.extent (Y_AXIS).length ();
333       
334       dy_f = dy_f_drul_[RIGHT] - dy_f_drul_[LEFT];
335       if (!fix_broken_b)
336         dy_f -= interstaff_f;
337
338       Real height_ratio_f = abs (height_f / width_f);
339       if (height_ratio_f > height_damp_f)
340         {
341           Direction d = (Direction)(- my_dir * (sign (dy_f)));
342           if (!d)
343             d = LEFT;
344           /* take third step */
345           Real damp_f = (height_ratio_f - height_damp_f) * width_f / 3;
346           /*
347             if y positions at about the same height, correct both ends
348           */
349           if (abs (dy_f / dx_f ) < slope_damp_f)
350             {
351               dy_f_drul_[-d] += my_dir * damp_f;
352               dy_f_drul_[d] += my_dir * damp_f;
353             }
354           /*
355             don't change slope too much, would have been catched by slope damping
356           */
357           else
358             {
359               damp_f = damp_f <? abs (dy_f/2);
360               dy_f_drul_[d] += my_dir * damp_f;
361             }
362         }
363     }
364
365   /*
366     If, after correcting, we're close to stem-end...
367   */
368   Drul_array<Real> snapy_f_drul;
369   snapy_f_drul[LEFT] = snapy_f_drul[RIGHT] = 0;
370   Drul_array<Real> snapx_f_drul;
371   snapx_f_drul[LEFT] = snapx_f_drul[RIGHT] = 0;
372   Drul_array<bool> snapped_b_drul;
373   snapped_b_drul[LEFT] = snapped_b_drul[RIGHT] = false;
374   do
375     {
376       Note_column * nc = note_column_drul[d];
377       if (nc == spanned_drul_[d]
378           && nc->stem_l ()
379           && nc->stem_l ()->get_direction () == my_dir
380           && abs (nc->stem_l ()->extent (Y_AXIS)[my_dir]
381                   - dy_f_drul_[d] + (d == LEFT ? 0 : interstaff_f))
382               <= snap_f)
383         {
384           /*
385             prepare to attach to stem-end
386           */
387           snapx_f_drul[d] = nc->stem_l ()->hpos_f ()
388             - spanned_drul_[d]->relative_coordinate (0, X_AXIS);
389
390           snapy_f_drul[d] = nc->stem_l ()->extent (Y_AXIS)[my_dir]
391             + interstaff_interval[d]
392             + my_dir * 2 * y_gap_f;
393           
394           snapped_b_drul[d] = true;
395         }
396     }
397   while (flip (&d) != LEFT);
398
399   /*
400     only use snapped positions if sign (dy) will not change
401     and dy doesn't change too much
402     */
403   if (!fix_broken_b)
404     dy_f += interstaff_f;
405
406
407   /*
408     (sigh)
409
410     More refactoring could be done.
411    */
412   Real maxsnap = abs (dy_f * snap_max_dy_f);
413   if (snapped_b_drul[LEFT] && snapped_b_drul[RIGHT]
414       && ((sign (snapy_f_drul[RIGHT] - snapy_f_drul[LEFT]) == sign (dy_f)))
415       && (!dy_f || (abs (snapy_f_drul[RIGHT] - snapy_f_drul[LEFT] - dy_f)
416                     < maxsnap)))
417     {
418       dy_f_drul_ = snapy_f_drul;
419       dx_f_drul_ = snapx_f_drul;
420     }
421   else
422     do
423       {
424         Direction od = (Direction)-d;
425         if (snapped_b_drul[d]
426             && d * sign (snapy_f_drul[d] - dy_f_drul_[od]) == sign (dy_f)
427             && (!dy_f || (abs (snapy_f_drul[d] - dy_f_drul_[od]  - d * dy_f)
428                           < maxsnap)))
429           {
430             dy_f_drul_[d] = snapy_f_drul[d];
431             dx_f_drul_[d] = snapx_f_drul[d];
432           }
433       }
434     while (flip (&d) != LEFT);
435 }
436
437
438 int
439 Slur::cross_staff_count ()const
440 {
441   Link_array<Note_column> encompass_arr =
442     Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
443
444   int k=0;
445
446   for (int i = 0; i < encompass_arr.size (); i++)
447     {
448       if (calc_interstaff_dist (encompass_arr[i], this))
449         k++;
450     }
451   return k;
452 }
453
454
455 Array<Offset>
456 Slur::get_encompass_offset_arr () const
457 {
458   Link_array<Note_column> encompass_arr =
459     Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
460   
461   Array<Offset> offset_arr;
462 #if 0
463   /*
464     check non-disturbed slur
465     FIXME: x of ends off by a tiny bit!!
466   */
467   offset_arr.push (Offset (0, dy_f_drul_[LEFT]));
468   offset_arr.push (Offset (0, dy_f_drul_[RIGHT]));
469   return offset_arr;
470 #endif
471   
472   Offset origin (relative_coordinate (0, X_AXIS), 0);
473
474   int first = 1;
475   int last = encompass_arr.size () - 2;
476
477   offset_arr.push (Offset (dx_f_drul_[LEFT], dy_f_drul_[LEFT]));
478
479   /*
480     left is broken edge
481   */
482
483   int cross_count  = cross_staff_count ();
484   bool cross_b = cross_count && cross_count < encompass_arr.size ();
485   if (encompass_arr[0] != spanned_drul_[LEFT])
486     {
487       first--;
488       Real is   = calc_interstaff_dist (encompass_arr[0], this);
489       if (cross_b)
490         offset_arr[0][Y_AXIS] += is;
491     }
492
493   /*
494     right is broken edge
495   */
496   if (encompass_arr.top () != spanned_drul_[RIGHT])
497     {
498       last++;
499     }
500
501   for (int i = first; i <= last; i++)
502     {
503       Offset o (encompass_offset (encompass_arr[i]));
504       offset_arr.push (o - origin);
505     }
506
507   offset_arr.push (Offset (spanner_length ()+  dx_f_drul_[RIGHT],
508                            dy_f_drul_[RIGHT]));
509
510   return offset_arr;
511 }
512
513
514 Array<Rod>
515 Slur::get_rods () const
516 {
517   Array<Rod> a;
518   Rod r;
519   r.item_l_drul_ = spanned_drul_;
520   r.distance_f_ = paper_l ()->get_var ("slur_x_minimum");
521
522   a.push (r);
523   return a;
524 }
525
526
527
528
529
530 Molecule*
531 Slur::do_brew_molecule_p () const
532 {
533   Real thick = paper_l ()->get_var ("slur_thickness");
534   Bezier one = get_curve ();
535
536   Molecule a;
537   SCM d =  get_elt_property ("dashed");
538   if (gh_number_p (d))
539     a = lookup_l ()->dashed_slur (one, thick, gh_scm2int (d));
540   else
541     a = lookup_l ()->slur (one, directional_element (this).get () * thick, thick);
542   
543   return new Molecule (a); 
544 }
545
546
547
548 Bezier
549 Slur::get_curve () const
550 {
551   Bezier_bow b (get_encompass_offset_arr (), directional_element (this).get ());
552
553   b.ratio_ = paper_l ()->get_var ("slur_ratio");
554   b.height_limit_ = paper_l ()->get_var ("slur_height_limit");
555   b.rc_factor_ = paper_l ()->get_var ("slur_rc_factor");
556
557   b.calculate ();
558   return b.get_curve ();
559 }
560
561 #if 0
562
563 /*
564   TODO: FIXME.
565  */
566
567 /*
568   Clipping
569
570   This function tries to address two issues:
571     * the tangents of the slur should always point inwards 
572       in the actual slur, i.e.  *after rotating back*.
573
574     * slurs shouldn't be too high 
575       let's try : h <= 1.2 b && h <= 3 staffheight?
576
577   We could calculate the tangent of the bezier curve from
578   both ends going inward, and clip the slur at the point
579   where the tangent (after rotation) points up (or inward
580   with a certain maximum angle).
581   
582   However, we assume that real clipping is not the best
583   answer.  We expect that moving the outer control point up 
584   if the slur becomes too high will result in a nicer slur 
585   after recalculation.
586
587   Knowing that the tangent is the line through the first
588   two control points, we'll clip (move the outer control
589   point upwards) too if the tangent points outwards.
590  */
591
592 bool
593 Bezier_bow::calc_clipping ()
594 {
595   Real clip_height = paper_l_->get_var ("slur_clip_height");
596   Real clip_ratio = paper_l_->get_var ("slur_clip_ratio");
597   Real clip_angle = paper_l_->get_var ("slur_clip_angle");
598
599   Real b = curve_.control_[3][X_AXIS] - curve_.control_[0][X_AXIS];
600   Real clip_h = clip_ratio * b <? clip_height;
601   Real begin_h = curve_.control_[1][Y_AXIS] - curve_.control_[0][Y_AXIS];
602   Real end_h = curve_.control_[2][Y_AXIS] - curve_.control_[3][Y_AXIS];
603   Real begin_dy = 0 >? begin_h - clip_h;
604   Real end_dy = 0 >? end_h - clip_h;
605   
606   Real pi = M_PI;
607   Real begin_alpha = (curve_.control_[1] - curve_.control_[0]).arg () + dir_ * alpha_;
608   Real end_alpha = pi -  (curve_.control_[2] - curve_.control_[3]).arg () - dir_  * alpha_;
609
610   Real max_alpha = clip_angle / 90 * pi / 2;
611   if ((begin_dy < 0) && (end_dy < 0)
612     && (begin_alpha < max_alpha) && (end_alpha < max_alpha))
613     return false;
614
615   transform_back ();
616
617   if ((begin_dy > 0) || (end_dy > 0))
618     {
619       Real dy = (begin_dy + end_dy) / 4;
620       dy *= cos (alpha_);
621       encompass_[0][Y_AXIS] += dir_ * dy;
622       encompass_.top ()[Y_AXIS] += dir_ * dy;
623     }
624   else
625     {
626       //ugh
627       Real c = 0.4;
628       if (begin_alpha >= max_alpha)
629         begin_dy = 0 >? c * begin_alpha / max_alpha * begin_h;
630       if (end_alpha >= max_alpha)
631         end_dy = 0 >? c * end_alpha / max_alpha * end_h;
632
633       encompass_[0][Y_AXIS] += dir_ * begin_dy;
634       encompass_.top ()[Y_AXIS] += dir_ * end_dy;
635
636       Offset delta = encompass_.top () - encompass_[0];
637       alpha_ = delta.arg ();
638     }
639
640   to_canonic_form ();
641
642   return true;
643 }
644 #endif
645
646