2 tie.cc -- implement Tie
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
12 #include "paper-def.hh"
14 #include "rhythmic-head.hh"
16 #include "paper-column.hh"
18 #include "staff-symbol-referencer.hh"
19 #include "directional-element-interface.hh"
20 #include "molecule.hh"
21 #include "bezier-bow.hh"
25 tie: Connect two noteheads.
29 c4 ~ \clef bass ; c4 or
33 do we have non-horizontal ties then?
38 Tie::set_head (Score_element
*me
,Direction d
, Item
* head_l
)
40 assert (!head (me
,d
));
41 index_set_cell (me
->get_elt_property ("heads"), d
, head_l
->self_scm ());
43 dynamic_cast<Spanner
*> (me
)->set_bound (d
, head_l
);
44 me
->add_dependency (head_l
);
48 Tie::set_interface (Score_element
*me
)
50 me
->set_elt_property ("heads", gh_cons (SCM_EOL
, SCM_EOL
));
51 me
->set_interface (ly_symbol2scm ("tie-interface"));
55 Tie::has_interface (Score_element
*me
)
57 return me
->has_interface (ly_symbol2scm ("tie-interface"));
61 Tie::head (Score_element
*me
, Direction d
)
63 SCM c
= me
->get_elt_property ("heads");
64 c
= index_cell (c
, d
);
66 return unsmob_element (c
);
70 Tie::position_f (Score_element
*me
)
72 Direction d
= head (me
,LEFT
) ? LEFT
:RIGHT
;
73 return Staff_symbol_referencer::position_f (head (me
,d
));
78 The direction of the Tie is more complicated (See [Ross] p136 and
79 further), the case of multiple ties is handled by Tie_column.
82 Tie::get_default_dir (Score_element
*me
)
84 Item
* sl
= head(me
,LEFT
) ? Rhythmic_head::stem_l (head (me
,LEFT
)) :0;
85 Item
* sr
= head(me
,RIGHT
) ? Rhythmic_head::stem_l (head (me
,RIGHT
)) :0;
90 if (Directional_element_interface (sl
).get () == UP
91 && Directional_element_interface (sr
).get () == UP
)
96 Item
*s
= sl
? sl
: sr
;
97 return - Directional_element_interface (s
). get ();
106 Tie::get_control_points (SCM smob
)
108 Spanner
*me
= dynamic_cast<Spanner
*> (unsmob_element (smob
));
109 Direction headdir
= CENTER
;
112 else if (head(me
,RIGHT
))
116 programming_error ("Tie without heads.");
118 return SCM_UNSPECIFIED
;
121 if (!Directional_element_interface (me
).get ())
122 Directional_element_interface (me
).set (Tie::get_default_dir (me
));
124 Real staff_space
= Staff_symbol_referencer::staff_space (me
);
126 Real x_gap_f
= me
->paper_l ()->get_var ("tie_x_gap");
128 Score_element
* commonx
= me
->common_refpoint (me
->get_bound (LEFT
), X_AXIS
);
129 commonx
= me
->common_refpoint (me
->get_bound (RIGHT
), X_AXIS
);
131 Score_element
* l
= me
->get_bound (LEFT
);
132 Score_element
* r
= me
->get_bound (RIGHT
);
133 Real width
= r
->relative_coordinate (commonx
, X_AXIS
)
134 + r
->extent (X_AXIS
)[LEFT
]
135 - l
->relative_coordinate (commonx
, X_AXIS
)
136 - l
->extent (X_AXIS
)[RIGHT
]
139 Real left_x
= l
->extent (X_AXIS
)[RIGHT
] + x_gap_f
;
141 Direction dir
= Directional_element_interface (me
).get();
143 Real h_inf
= me
->paper_l ()->get_var ("tie_height_limit_factor") * staff_space
;
144 Real r_0
= me
->paper_l ()->get_var ("tie_ratio");
147 Bezier b
= slur_shape (width
, h_inf
, r_0
);
149 Offset leave_dir
= b
.control_
[1] - b
.control_
[0];
151 Real dx
= (head (me
, headdir
)->extent (X_AXIS
).length () + x_gap_f
)/2.0;
152 Real max_gap
= leave_dir
[Y_AXIS
] * dx
/ leave_dir
[X_AXIS
];
155 for small ties (t small) we want to start in the Y-center (so dy = 0), for
156 large ties, the tie should appear to come from the center of the
157 head, so dy = max_gap
159 maybe use a different formula?
161 TODO: what if 2 heads have different size.
163 TODO: for small ties, it is better to start over the heads
164 iso. next to the heads.
166 Real t
= (width
/ staff_space
- 5.0); // ugh.
167 Real dy
= t
> 0 ? max_gap
* sqr (t
/ (1 + t
)) : 0.0;
169 Real ypos
= Tie::position_f (me
) * staff_space
/2 + dir
* dy
;
172 todo: prevent ending / staffline collision.
174 todo: tie / stem collision
177 b
= slur_shape(width
,h_inf
, r_0
);
179 b
.translate (Offset (left_x
, ypos
));
183 Avoid colliding of the horizontal part with stafflines.
185 should do me for slurs as well.
188 Array
<Real
> horizontal (b
.solve_derivative (Offset (1,0)));
189 if (horizontal
.size ())
192 ugh. Doesnt work for non-horizontal curves.
194 Real y
= b
.curve_point (horizontal
[0])[Y_AXIS
];
196 Real ry
= rint (y
/staff_space
) * staff_space
;
199 if (fabs (y
) <= Staff_symbol_referencer::staff_radius (me
)
200 && fabs (diff
) < me
->paper_l ()->get_var ("tie_staffline_clearance"))
202 newy
= ry
- 0.5 * staff_space
* sign (diff
) ;
205 Real y0
= b
.control_
[0][Y_AXIS
];
206 b
.control_
[2][Y_AXIS
] =
207 b
.control_
[1][Y_AXIS
] =
208 (b
.control_
[1][Y_AXIS
] - y0
) * ((newy
- y0
) / (y
- y0
)) + y0
;
211 programming_error ("Tie is nowhere horizontal");
215 SCM controls
= SCM_EOL
;
217 controls
= gh_cons ( ly_offset2scm (b
.control_
[i
]), controls
);
221 MAKE_SCHEME_CALLBACK(Tie
,set_spacing_rods
);
224 TODO: set minimum distances for begin/end of line
227 Tie::set_spacing_rods (SCM smob
)
229 Score_element
*me
= unsmob_element (smob
);
230 Spanner
*sp
= dynamic_cast<Spanner
*> (me
);
233 r
.item_l_drul_
[LEFT
]=sp
->get_bound (LEFT
);
234 r
.item_l_drul_
[RIGHT
]=sp
->get_bound (RIGHT
);
237 = gh_scm2double (me
->get_elt_property ("minimum-length"))
238 * me
->paper_l ()->get_var ("staffspace");
240 return SCM_UNSPECIFIED
;
243 MAKE_SCHEME_CALLBACK(Tie
,brew_molecule
);
245 Tie::brew_molecule (SCM smob
)
247 Score_element
*me
= unsmob_element (smob
);
249 SCM cp
= me
->get_elt_property ("control-points");
252 cp
= get_control_points (smob
);
253 me
->set_elt_property ("control-points", cp
);
257 gh_scm2double (me
->get_elt_property ("thickness"))
258 * me
->paper_l ()->get_var ("stafflinethickness");
262 for (SCM s
= cp
; s
!= SCM_EOL
; s
= gh_cdr (s
))
264 b
.control_
[i
] = ly_scm2offset (gh_car (s
));
268 Molecule a
= me
->lookup_l ()->slur (b
, Directional_element_interface (me
).get () * thick
, thick
);
270 return a
.create_scheme ();