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 ());
331 Real height_f = c.extent (X_AXIS).length ();
332 Real width_f = c.extent (Y_AXIS).length ();
334 dy_f = dy_f_drul_[RIGHT] - dy_f_drul_[LEFT];
336 dy_f -= interstaff_f;
338 Real height_ratio_f = abs (height_f / width_f);
339 if (height_ratio_f > height_damp_f)
341 Direction d = (Direction)(- my_dir * (sign (dy_f)));
344 /* take third step */
345 Real damp_f = (height_ratio_f - height_damp_f) * width_f / 3;
347 if y positions at about the same height, correct both ends
349 if (abs (dy_f / dx_f ) < slope_damp_f)
351 dy_f_drul_[-d] += my_dir * damp_f;
352 dy_f_drul_[d] += my_dir * damp_f;
355 don't change slope too much, would have been catched by slope damping
359 damp_f = damp_f <? abs (dy_f/2);
360 dy_f_drul_[d] += my_dir * damp_f;
366 If, after correcting, we're close to stem-end...
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;
376 Note_column * nc = note_column_drul[d];
377 if (nc == spanned_drul_[d]
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))
385 prepare to attach to stem-end
387 snapx_f_drul[d] = nc->stem_l ()->hpos_f ()
388 - spanned_drul_[d]->relative_coordinate (0, X_AXIS);
390 snapy_f_drul[d] = nc->stem_l ()->extent (Y_AXIS)[my_dir]
391 + interstaff_interval[d]
392 + my_dir * 2 * y_gap_f;
394 snapped_b_drul[d] = true;
397 while (flip (&d) != LEFT);
400 only use snapped positions if sign (dy) will not change
401 and dy doesn't change too much
404 dy_f += interstaff_f;
410 More refactoring could be done.
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)
418 dy_f_drul_ = snapy_f_drul;
419 dx_f_drul_ = snapx_f_drul;
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)
430 dy_f_drul_[d] = snapy_f_drul[d];
431 dx_f_drul_[d] = snapx_f_drul[d];
434 while (flip (&d) != LEFT);
439 Slur::cross_staff_count ()const
441 Link_array<Note_column> encompass_arr =
442 Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
446 for (int i = 0; i < encompass_arr.size (); i++)
448 if (calc_interstaff_dist (encompass_arr[i], this))
456 Slur::get_encompass_offset_arr () const
458 Link_array<Note_column> encompass_arr =
459 Group_interface__extract_elements (this, (Note_column*)0, "note-columns");
461 Array<Offset> offset_arr;
464 check non-disturbed slur
465 FIXME: x of ends off by a tiny bit!!
467 offset_arr.push (Offset (0, dy_f_drul_[LEFT]));
468 offset_arr.push (Offset (0, dy_f_drul_[RIGHT]));
472 Offset origin (relative_coordinate (0, X_AXIS), 0);
475 int last = encompass_arr.size () - 2;
477 offset_arr.push (Offset (dx_f_drul_[LEFT], dy_f_drul_[LEFT]));
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])
488 Real is = calc_interstaff_dist (encompass_arr[0], this);
490 offset_arr[0][Y_AXIS] += is;
496 if (encompass_arr.top () != spanned_drul_[RIGHT])
501 for (int i = first; i <= last; i++)
503 Offset o (encompass_offset (encompass_arr[i]));
504 offset_arr.push (o - origin);
507 offset_arr.push (Offset (spanner_length ()+ dx_f_drul_[RIGHT],
515 Slur::get_rods () const
519 r.item_l_drul_ = spanned_drul_;
520 r.distance_f_ = paper_l ()->get_var ("slur_x_minimum");
531 Slur::do_brew_molecule_p () const
533 Real thick = paper_l ()->get_var ("slur_thickness");
534 Bezier one = get_curve ();
537 SCM d = get_elt_property ("dashed");
539 a = lookup_l ()->dashed_slur (one, thick, gh_scm2int (d));
541 a = lookup_l ()->slur (one, directional_element (this).get () * thick, thick);
543 return new Molecule (a);
549 Slur::get_curve () const
551 Bezier_bow b (get_encompass_offset_arr (), directional_element (this).get ());
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");
558 return b.get_curve ();
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*.
574 * slurs shouldn't be too high
575 let's try : h <= 1.2 b && h <= 3 staffheight?
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).
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
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.
593 Bezier_bow::calc_clipping ()
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");
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;
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_;
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))
617 if ((begin_dy > 0) || (end_dy > 0))
619 Real dy = (begin_dy + end_dy) / 4;
621 encompass_[0][Y_AXIS] += dir_ * dy;
622 encompass_.top ()[Y_AXIS] += dir_ * dy;
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;
633 encompass_[0][Y_AXIS] += dir_ * begin_dy;
634 encompass_.top ()[Y_AXIS] += dir_ * end_dy;
636 Offset delta = encompass_.top () - encompass_[0];
637 alpha_ = delta.arg ();