]> git.donarmstrong.com Git - lilypond.git/blob - lily/slur.cc
release: 1.3.20
[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       Offset size (c.extent (X_AXIS).length (),
331                    c.extent (Y_AXIS).length ());
332
333       dy_f = dy_f_drul_[RIGHT] - dy_f_drul_[LEFT];
334       if (!fix_broken_b)
335         dy_f -= interstaff_f;
336
337       Real height_ratio_f = abs (size[Y_AXIS] / size[X_AXIS]);
338       if (height_ratio_f > height_damp_f)
339         {
340           Direction d = (Direction)(- my_dir * (sign (dy_f)));
341           if (!d)
342             d = LEFT;
343           /* take third step */
344           Real damp_f = (height_ratio_f - height_damp_f) * size[X_AXIS] / 3;
345           /*
346             if y positions at about the same height, correct both ends
347           */
348           if (abs (dy_f / dx_f ) < slope_damp_f)
349             {
350               dy_f_drul_[-d] += my_dir * damp_f;
351               dy_f_drul_[d] += my_dir * damp_f;
352             }
353           /*
354             don't change slope too much, would have been catched by slope damping
355           */
356           else
357             {
358               damp_f = damp_f <? abs (dy_f/2);
359               dy_f_drul_[d] += my_dir * damp_f;
360             }
361         }
362     }
363
364   /*
365     If, after correcting, we're close to stem-end...
366   */
367   Drul_array<Real> snapy_f_drul;
368   snapy_f_drul[LEFT] = snapy_f_drul[RIGHT] = 0;
369   Drul_array<Real> snapx_f_drul;
370   snapx_f_drul[LEFT] = snapx_f_drul[RIGHT] = 0;
371   Drul_array<bool> snapped_b_drul;
372   snapped_b_drul[LEFT] = snapped_b_drul[RIGHT] = false;
373   do
374     {
375       Note_column * nc = note_column_drul[d];
376       if (nc == spanned_drul_[d]
377           && nc->stem_l ()
378           && nc->stem_l ()->get_direction () == my_dir
379           && abs (nc->stem_l ()->extent (Y_AXIS)[my_dir]
380                   - dy_f_drul_[d] + (d == LEFT ? 0 : interstaff_f))
381               <= snap_f)
382         {
383           /*
384             prepare to attach to stem-end
385           */
386           snapx_f_drul[d] = nc->stem_l ()->hpos_f ()
387             - spanned_drul_[d]->relative_coordinate (0, X_AXIS);
388
389           snapy_f_drul[d] = nc->stem_l ()->extent (Y_AXIS)[my_dir]
390             + interstaff_interval[d]
391             + my_dir * 2 * y_gap_f;
392           
393           snapped_b_drul[d] = true;
394         }
395     }
396   while (flip (&d) != LEFT);
397
398   /*
399     only use snapped positions if sign (dy) will not change
400     and dy doesn't change too much
401     */
402   if (!fix_broken_b)
403     dy_f += interstaff_f;
404
405
406   /*
407     (sigh)
408
409     More refactoring could be done.
410    */
411   Real maxsnap = abs (dy_f * snap_max_dy_f);
412   if (snapped_b_drul[LEFT] && snapped_b_drul[RIGHT]
413       && ((sign (snapy_f_drul[RIGHT] - snapy_f_drul[LEFT]) == sign (dy_f)))
414       && (!dy_f || (abs (snapy_f_drul[RIGHT] - snapy_f_drul[LEFT] - dy_f)
415                     < maxsnap)))
416     {
417       dy_f_drul_ = snapy_f_drul;
418       dx_f_drul_ = snapx_f_drul;
419     }
420   else
421     do
422       {
423         Direction od = (Direction)-d;
424         if (snapped_b_drul[d]
425             && d * sign (snapy_f_drul[d] - dy_f_drul_[od]) == sign (dy_f)
426             && (!dy_f || (abs (snapy_f_drul[d] - dy_f_drul_[od]  - d * dy_f)
427                           < maxsnap)))
428           {
429             dy_f_drul_[d] = snapy_f_drul[d];
430             dx_f_drul_[d] = snapx_f_drul[d];
431           }
432       }
433     while (flip (&d) != LEFT);
434 }
435
436
437 int
438 Slur::cross_staff_count ()const
439 {
440   Link_array<Note_column> encompass_arr =
441     Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
442
443   int k=0;
444
445   for (int i = 0; i < encompass_arr.size (); i++)
446     {
447       if (calc_interstaff_dist (encompass_arr[i], this))
448         k++;
449     }
450   return k;
451 }
452
453
454 Array<Offset>
455 Slur::get_encompass_offset_arr () const
456 {
457   Link_array<Note_column> encompass_arr =
458     Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
459   
460   Array<Offset> offset_arr;
461 #if 0
462   /*
463     check non-disturbed slur
464     FIXME: x of ends off by a tiny bit!!
465   */
466   offset_arr.push (Offset (0, dy_f_drul_[LEFT]));
467   offset_arr.push (Offset (0, dy_f_drul_[RIGHT]));
468   return offset_arr;
469 #endif
470   
471   Offset origin (relative_coordinate (0, X_AXIS), 0);
472
473   int first = 1;
474   int last = encompass_arr.size () - 2;
475
476   offset_arr.push (Offset (dx_f_drul_[LEFT], dy_f_drul_[LEFT]));
477
478   /*
479     left is broken edge
480   */
481
482   int cross_count  = cross_staff_count ();
483   bool cross_b = cross_count && cross_count < encompass_arr.size ();
484   if (encompass_arr[0] != spanned_drul_[LEFT])
485     {
486       first--;
487       Real is   = calc_interstaff_dist (encompass_arr[0], this);
488       if (cross_b)
489         offset_arr[0][Y_AXIS] += is;
490     }
491
492   /*
493     right is broken edge
494   */
495   if (encompass_arr.top () != spanned_drul_[RIGHT])
496     {
497       last++;
498     }
499
500   for (int i = first; i <= last; i++)
501     {
502       Offset o (encompass_offset (encompass_arr[i]));
503       offset_arr.push (o - origin);
504     }
505
506   offset_arr.push (Offset (spanner_length ()+  dx_f_drul_[RIGHT],
507                            dy_f_drul_[RIGHT]));
508
509   return offset_arr;
510 }
511
512
513 Array<Rod>
514 Slur::get_rods () const
515 {
516   Array<Rod> a;
517   Rod r;
518   r.item_l_drul_ = spanned_drul_;
519   r.distance_f_ = paper_l ()->get_var ("slur_x_minimum");
520
521   a.push (r);
522   return a;
523 }
524
525
526
527
528
529 Molecule*
530 Slur::do_brew_molecule_p () const
531 {
532   Real thick = paper_l ()->get_var ("slur_thickness");
533   Bezier one = get_curve ();
534
535   Molecule a;
536   SCM d =  get_elt_property ("dashed");
537   if (gh_number_p (d))
538     a = lookup_l ()->dashed_slur (one, thick, gh_scm2int (d));
539   else
540     a = lookup_l ()->slur (one, directional_element (this).get () * thick, thick);
541   
542   return new Molecule (a); 
543 }
544
545
546
547 Bezier
548 Slur::get_curve () const
549 {
550   Bezier_bow b (get_encompass_offset_arr (), directional_element (this).get ());
551
552   b.ratio_ = paper_l ()->get_var ("slur_ratio");
553   b.height_limit_ = paper_l ()->get_var ("slur_height_limit");
554   b.rc_factor_ = paper_l ()->get_var ("slur_rc_factor");
555
556   b.calculate ();
557   return b.get_curve ();
558 }
559
560 #if 0
561
562 /*
563   TODO: FIXME.
564  */
565
566 /*
567   Clipping
568
569   This function tries to address two issues:
570     * the tangents of the slur should always point inwards 
571       in the actual slur, i.e.  *after rotating back*.
572
573     * slurs shouldn't be too high 
574       let's try : h <= 1.2 b && h <= 3 staffheight?
575
576   We could calculate the tangent of the bezier curve from
577   both ends going inward, and clip the slur at the point
578   where the tangent (after rotation) points up (or inward
579   with a certain maximum angle).
580   
581   However, we assume that real clipping is not the best
582   answer.  We expect that moving the outer control point up 
583   if the slur becomes too high will result in a nicer slur 
584   after recalculation.
585
586   Knowing that the tangent is the line through the first
587   two control points, we'll clip (move the outer control
588   point upwards) too if the tangent points outwards.
589  */
590
591 bool
592 Bezier_bow::calc_clipping ()
593 {
594   Real clip_height = paper_l_->get_var ("slur_clip_height");
595   Real clip_ratio = paper_l_->get_var ("slur_clip_ratio");
596   Real clip_angle = paper_l_->get_var ("slur_clip_angle");
597
598   Real b = curve_.control_[3][X_AXIS] - curve_.control_[0][X_AXIS];
599   Real clip_h = clip_ratio * b <? clip_height;
600   Real begin_h = curve_.control_[1][Y_AXIS] - curve_.control_[0][Y_AXIS];
601   Real end_h = curve_.control_[2][Y_AXIS] - curve_.control_[3][Y_AXIS];
602   Real begin_dy = 0 >? begin_h - clip_h;
603   Real end_dy = 0 >? end_h - clip_h;
604   
605   Real pi = M_PI;
606   Real begin_alpha = (curve_.control_[1] - curve_.control_[0]).arg () + dir_ * alpha_;
607   Real end_alpha = pi -  (curve_.control_[2] - curve_.control_[3]).arg () - dir_  * alpha_;
608
609   Real max_alpha = clip_angle / 90 * pi / 2;
610   if ((begin_dy < 0) && (end_dy < 0)
611     && (begin_alpha < max_alpha) && (end_alpha < max_alpha))
612     return false;
613
614   transform_back ();
615
616   if ((begin_dy > 0) || (end_dy > 0))
617     {
618       Real dy = (begin_dy + end_dy) / 4;
619       dy *= cos (alpha_);
620       encompass_[0][Y_AXIS] += dir_ * dy;
621       encompass_.top ()[Y_AXIS] += dir_ * dy;
622     }
623   else
624     {
625       //ugh
626       Real c = 0.4;
627       if (begin_alpha >= max_alpha)
628         begin_dy = 0 >? c * begin_alpha / max_alpha * begin_h;
629       if (end_alpha >= max_alpha)
630         end_dy = 0 >? c * end_alpha / max_alpha * end_h;
631
632       encompass_[0][Y_AXIS] += dir_ * begin_dy;
633       encompass_.top ()[Y_AXIS] += dir_ * end_dy;
634
635       Offset delta = encompass_.top () - encompass_[0];
636       alpha_ = delta.arg ();
637     }
638
639   to_canonic_form ();
640
641   return true;
642 }
643 #endif
644
645