2 slur.cc -- implement Slur
4 source file of the GNU LilyPond music typesetter
6 (c) 1996, 1997--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
12 * begin and end should be treated as a/acknowledge Scripts.
13 * broken slur should have uniform trend
16 #include "directional-element-interface.hh"
17 #include "group-interface.hh"
20 #include "paper-def.hh"
21 #include "note-column.hh"
23 #include "paper-column.hh"
24 #include "molecule.hh"
28 #include "bezier-bow.hh"
30 #include "cross-staff.hh"
31 #include "group-interface.hh"
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);
41 Slur::add_column (Note_column*n)
43 if (!gh_pair_p (n->get_elt_property ("note-heads")))
44 warning (_ ("Putting slur over rest. Ignoring."));
47 Group_interface gi (this, "note-columns");
54 Slur::get_default_dir () const
56 Link_array<Note_column> encompass_arr =
57 Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
60 for (int i=0; i < encompass_arr.size (); i ++)
62 if (encompass_arr[i]->dir () < 0)
72 Slur::do_add_processing ()
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 ());
84 Slur::encompass_offset (Note_column const* col) const
87 Stem* stem_l = col->stem_l ();
88 Direction dir = directional_element (this).get ();
92 warning (_ ("Slur over rest?"));
93 o[X_AXIS] = col->hpos_f ();
94 o[Y_AXIS] = col->extent (Y_AXIS)[dir];
97 Direction stem_dir = directional_element (stem_l).get ();
98 o[X_AXIS] = stem_l->hpos_f ();
101 Simply set x to middle of notehead
104 o[X_AXIS] -= 0.5 * stem_dir * col->extent (X_AXIS).length ();
106 if ((stem_dir == dir)
107 && !stem_l->extent (Y_AXIS).empty_b ())
109 o[Y_AXIS] = stem_l->extent (Y_AXIS)[dir];
113 o[Y_AXIS] = col->extent (Y_AXIS)[dir];
117 leave a gap: slur mustn't touch head/stem
119 o[Y_AXIS] += dir * paper_l ()->get_var ("slur_y_free");
120 o[Y_AXIS] -= calc_interstaff_dist (stem_l, this);
130 Slur::do_post_processing ()
132 Link_array<Note_column> encompass_arr =
133 Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
135 if (!encompass_arr.size ())
137 set_elt_property ("transparent", SCM_BOOL_T);
143 if (!directional_element (this).get ())
144 directional_element (this).set (get_default_dir ());
147 Slur and tie placement [OSU]
150 * x = centre of head - d * x_gap_f
153 * y = length < 5ss : horizontal tangent + d * 0.25 ss
154 y = length >= 5ss : y next interline - d * 0.25 ss
157 Real staff_space = paper_l ()->get_var ("interline");
158 Real half_staff_space = staff_space / 2;
160 Real x_gap_f = paper_l ()->get_var ("slur_x_gap");
161 Real y_gap_f = paper_l ()->get_var ("slur_y_gap");
163 Drul_array<Note_column*> note_column_drul;
164 note_column_drul[LEFT] = encompass_arr[0];
165 note_column_drul[RIGHT] = encompass_arr.top ();
167 bool fix_broken_b = false;
169 Direction my_dir = directional_element (this).get ();
177 if ((note_column_drul[d] == spanned_drul_[d])
178 && note_column_drul[d]->first_head ()
179 && (note_column_drul[d]->stem_l ()))
181 Stem* stem_l = note_column_drul[d]->stem_l ();
183 side directly attached to note head;
184 no beam getting in the way
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)))
191 dx_f_drul_[d] = spanned_drul_[d]->extent (X_AXIS).length () / 2;
192 dx_f_drul_[d] -= d * x_gap_f;
194 if (stem_l->get_direction () != my_dir)
196 dy_f_drul_[d] = note_column_drul[d]->extent (Y_AXIS)[my_dir];
200 dy_f_drul_[d] = stem_l->chord_start_f ()
201 + my_dir * half_staff_space;
203 dy_f_drul_[d] += my_dir * y_gap_f;
206 side attached to (visible) stem
210 dx_f_drul_[d] = stem_l->hpos_f ()
211 - spanned_drul_[d]->relative_coordinate (0, X_AXIS);
213 side attached to beamed stem
215 if (stem_l->beam_l () && (stem_l->beam_count (-d) >= 1))
217 dy_f_drul_[d] = stem_l->extent (Y_AXIS)[my_dir];
218 dy_f_drul_[d] += my_dir * 2 * y_gap_f;
221 side attached to notehead, with stem getting in the way
225 dx_f_drul_[d] -= d * x_gap_f;
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;
238 dx_f_drul_[d] = get_broken_left_end_align ();
241 broken: should get y from other piece, so that slur
242 continues up/down trend
244 for now: be horizontal..
249 while (flip (&d) != LEFT);
251 int cross_count = cross_staff_count ();
252 bool interstaff_b = (0 < cross_count) && (cross_count < encompass_arr.size ());
254 Drul_array<Offset> info_drul;
255 Drul_array<Real> interstaff_interval;
259 info_drul[d] = encompass_offset (encompass_arr.boundary (d, 0));
260 interstaff_interval[d] = - calc_interstaff_dist (encompass_arr.boundary (d,0),
263 while (flip (&d) != LEFT);
265 Real interstaff_f = interstaff_interval[RIGHT] - interstaff_interval[LEFT];
269 Direction d = (encompass_arr.top () != spanned_drul_[RIGHT]) ?
271 dy_f_drul_[d] = info_drul[d][Y_AXIS];
274 dy_f_drul_[d] -= interstaff_interval[d];
275 if (cross_count) // interstaff_i ?
277 dy_f_drul_[LEFT] += interstaff_interval[d];
278 dy_f_drul_[RIGHT] += interstaff_interval[d];
285 Now we've got a fine slur
286 Catch and correct some ugly cases
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");
295 dy_f_drul_[RIGHT] += interstaff_f;
297 Real dy_f = dy_f_drul_[RIGHT] - dy_f_drul_[LEFT];
299 dy_f -= interstaff_f;
300 Real dx_f = spanner_length ()+ dx_f_drul_[RIGHT] - dx_f_drul_[LEFT];
303 Avoid too steep slurs.
305 Real slope_ratio_f = abs (dy_f / dx_f);
306 if (slope_ratio_f > slope_damp_f)
308 Direction d = (Direction)(- my_dir * (sign (dy_f)));
311 Real damp_f = (slope_ratio_f - slope_damp_f) * dx_f;
313 must never change sign of dy
315 damp_f = damp_f <? abs (dy_f);
316 dy_f_drul_[d] += my_dir * damp_f;
322 Wierd slurs may look a lot better after they have been
324 So, we'll do this in 3 steps
326 for (int i = 0; i < 3; i++)
328 Bezier c (get_curve ());
330 Offset size (c.extent (X_AXIS).length (),
331 c.extent (Y_AXIS).length ());
333 dy_f = dy_f_drul_[RIGHT] - dy_f_drul_[LEFT];
335 dy_f -= interstaff_f;
337 Real height_ratio_f = abs (size[Y_AXIS] / size[X_AXIS]);
338 if (height_ratio_f > height_damp_f)
340 Direction d = (Direction)(- my_dir * (sign (dy_f)));
343 /* take third step */
344 Real damp_f = (height_ratio_f - height_damp_f) * size[X_AXIS] / 3;
346 if y positions at about the same height, correct both ends
348 if (abs (dy_f / dx_f ) < slope_damp_f)
350 dy_f_drul_[-d] += my_dir * damp_f;
351 dy_f_drul_[d] += my_dir * damp_f;
354 don't change slope too much, would have been catched by slope damping
358 damp_f = damp_f <? abs (dy_f/2);
359 dy_f_drul_[d] += my_dir * damp_f;
365 If, after correcting, we're close to stem-end...
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;
375 Note_column * nc = note_column_drul[d];
376 if (nc == spanned_drul_[d]
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))
384 prepare to attach to stem-end
386 snapx_f_drul[d] = nc->stem_l ()->hpos_f ()
387 - spanned_drul_[d]->relative_coordinate (0, X_AXIS);
389 snapy_f_drul[d] = nc->stem_l ()->extent (Y_AXIS)[my_dir]
390 + interstaff_interval[d]
391 + my_dir * 2 * y_gap_f;
393 snapped_b_drul[d] = true;
396 while (flip (&d) != LEFT);
399 only use snapped positions if sign (dy) will not change
400 and dy doesn't change too much
403 dy_f += interstaff_f;
409 More refactoring could be done.
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)
417 dy_f_drul_ = snapy_f_drul;
418 dx_f_drul_ = snapx_f_drul;
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)
429 dy_f_drul_[d] = snapy_f_drul[d];
430 dx_f_drul_[d] = snapx_f_drul[d];
433 while (flip (&d) != LEFT);
438 Slur::cross_staff_count ()const
440 Link_array<Note_column> encompass_arr =
441 Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
445 for (int i = 0; i < encompass_arr.size (); i++)
447 if (calc_interstaff_dist (encompass_arr[i], this))
455 Slur::get_encompass_offset_arr () const
457 Link_array<Note_column> encompass_arr =
458 Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
460 Array<Offset> offset_arr;
463 check non-disturbed slur
464 FIXME: x of ends off by a tiny bit!!
466 offset_arr.push (Offset (0, dy_f_drul_[LEFT]));
467 offset_arr.push (Offset (0, dy_f_drul_[RIGHT]));
471 Offset origin (relative_coordinate (0, X_AXIS), 0);
474 int last = encompass_arr.size () - 2;
476 offset_arr.push (Offset (dx_f_drul_[LEFT], dy_f_drul_[LEFT]));
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])
487 Real is = calc_interstaff_dist (encompass_arr[0], this);
489 offset_arr[0][Y_AXIS] += is;
495 if (encompass_arr.top () != spanned_drul_[RIGHT])
500 for (int i = first; i <= last; i++)
502 Offset o (encompass_offset (encompass_arr[i]));
503 offset_arr.push (o - origin);
506 offset_arr.push (Offset (spanner_length ()+ dx_f_drul_[RIGHT],
514 Slur::get_rods () const
518 r.item_l_drul_ = spanned_drul_;
519 r.distance_f_ = paper_l ()->get_var ("slur_x_minimum");
530 Slur::do_brew_molecule_p () const
532 Real thick = paper_l ()->get_var ("slur_thickness");
533 Bezier one = get_curve ();
536 SCM d = get_elt_property ("dashed");
538 a = lookup_l ()->dashed_slur (one, thick, gh_scm2int (d));
540 a = lookup_l ()->slur (one, directional_element (this).get () * thick, thick);
542 return new Molecule (a);
548 Slur::get_curve () const
550 Bezier_bow b (get_encompass_offset_arr (), directional_element (this).get ());
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");
557 return b.get_curve ();
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*.
573 * slurs shouldn't be too high
574 let's try : h <= 1.2 b && h <= 3 staffheight?
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).
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
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.
592 Bezier_bow::calc_clipping ()
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");
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;
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_;
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))
616 if ((begin_dy > 0) || (end_dy > 0))
618 Real dy = (begin_dy + end_dy) / 4;
620 encompass_[0][Y_AXIS] += dir_ * dy;
621 encompass_.top ()[Y_AXIS] += dir_ * dy;
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;
632 encompass_[0][Y_AXIS] += dir_ * begin_dy;
633 encompass_.top ()[Y_AXIS] += dir_ * end_dy;
635 Offset delta = encompass_.top () - encompass_[0];
636 alpha_ = delta.arg ();