]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie.cc
release: 1.3.72
[lilypond.git] / lily / tie.cc
1 /*
2   tie.cc -- implement Tie
3
4   source file of the GNU LilyPond music typesetter
5
6   (c)  1997--2000 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8 #include <math.h>
9
10 #include "spanner.hh"
11 #include "lookup.hh"
12 #include "paper-def.hh"
13 #include "tie.hh"
14 #include "rhythmic-head.hh"
15 #include "bezier.hh"
16 #include "paper-column.hh"
17 #include "debug.hh"
18 #include "staff-symbol-referencer.hh"
19 #include "directional-element-interface.hh"
20 #include "molecule.hh"
21 #include "bezier-bow.hh"
22 #include "stem.hh"
23
24 /*
25   tie: Connect two noteheads.
26
27   What if we have
28
29   c4 ~ \clef bass ; c4 or
30
31   c4 \staffchange c4
32
33   do we have non-horizontal ties then?
34   */
35
36
37 void
38 Tie::set_head (Score_element*me,Direction d, Item * head_l)
39 {
40   assert (!head (me,d));
41   index_set_cell (me->get_elt_property ("heads"), d, head_l->self_scm_);
42   
43   dynamic_cast<Spanner*> (me)->set_bound (d, head_l);
44   me->add_dependency (head_l);
45 }
46
47 void
48 Tie::set_interface (Score_element*me)
49 {
50   me->set_elt_property ("heads", gh_cons (SCM_EOL, SCM_EOL));
51   me->set_interface (ly_symbol2scm ("tie-interface"));
52 }
53
54 bool
55 Tie::has_interface (Score_element*me)
56 {
57   return me->has_interface (ly_symbol2scm ("tie-interface"));
58 }
59
60 Score_element*
61 Tie::head (Score_element*me, Direction d) 
62 {
63   SCM c = me->get_elt_property ("heads");
64   c = index_cell (c, d);
65
66   return unsmob_element (c);
67 }
68
69 Real
70 Tie::position_f (Score_element*me) 
71 {
72   Direction d = head (me,LEFT) ? LEFT:RIGHT;
73   return Staff_symbol_referencer::position_f (head (me,d));
74 }
75
76
77 /*
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.
80 */
81 Direction
82 Tie::get_default_dir (Score_element*me) 
83 {
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;  
86
87   if (sl && Directional_element_interface (sl).get () == UP
88       && sr && Directional_element_interface (sr).get () == UP)
89     return DOWN;
90   else
91     return UP;
92 }
93
94
95 SCM
96 Tie::get_control_points (SCM smob)
97 {  
98   Spanner*me = dynamic_cast<Spanner*> (unsmob_element (smob));
99   Direction headdir = CENTER; 
100   if (head (me,LEFT))
101     headdir = LEFT;
102   else if (head(me,RIGHT))
103     headdir = RIGHT;
104   else
105     {
106       programming_error ("Tie without heads.");
107       me->suicide ();
108       return SCM_UNSPECIFIED;
109     }
110   
111   if (!Directional_element_interface (me).get ())
112     Directional_element_interface (me).set (Tie::get_default_dir (me));
113   
114   Real staff_space = Staff_symbol_referencer::staff_space (me);
115
116   Real x_gap_f = me->paper_l ()->get_var ("tie_x_gap");
117
118   Score_element* commonx = me->common_refpoint (me->get_bound (LEFT), X_AXIS);
119   commonx = me->common_refpoint (me->get_bound (RIGHT), X_AXIS);
120   
121   Score_element* l = me->get_bound (LEFT);
122   Score_element* r = me->get_bound (RIGHT);  
123   Real width = r->relative_coordinate (commonx, X_AXIS)
124     + r->extent (X_AXIS)[LEFT]
125     - l->relative_coordinate (commonx, X_AXIS)
126     - l->extent (X_AXIS)[RIGHT]
127     -2* x_gap_f;
128
129   Real left_x = l->extent (X_AXIS)[RIGHT] + x_gap_f;
130   
131   Direction dir = Directional_element_interface (me).get();
132   
133   Real h_inf = me->paper_l ()->get_var ("tie_height_limit_factor") * staff_space;
134   Real r_0 = me->paper_l ()->get_var ("tie_ratio");
135
136
137   Bezier b  = slur_shape (width, h_inf, r_0);
138   
139   Offset leave_dir = b.control_[1] - b.control_[0];
140
141   Real dx = (head (me, headdir)->extent (X_AXIS).length () + x_gap_f)/2.0;
142   Real max_gap = leave_dir[Y_AXIS] * dx / leave_dir[X_AXIS];
143
144   /*
145     for small ties (t small) we want to start in the Y-center (so dy = 0), for
146     large ties, the tie should appear to come from the center of the
147     head, so dy = max_gap
148
149     maybe use a different formula?
150
151     TODO: what if 2 heads have different size.
152
153     TODO: for small ties, it is better to start over the heads
154     iso. next to the heads. 
155   */
156   Real t = (width / staff_space - 5.0); // ugh.
157   Real dy = t > 0 ? max_gap * sqr (t / (1 + t)) : 0.0;
158
159   Real ypos = Tie::position_f (me) * staff_space/2 + dir * dy;
160
161   /*
162     todo: prevent ending / staffline collision.
163
164     todo: tie / stem collision
165    */
166
167   b = slur_shape(width,h_inf, r_0);
168   b.scale (1, dir);
169   b.translate (Offset (left_x, ypos));
170   
171
172   /*
173     Avoid colliding of the horizontal part with stafflines.
174     
175     should do me for slurs as well.
176
177    */
178   Array<Real> horizontal (b.solve_derivative (Offset (1,0)));
179   if (horizontal.size ())
180     {
181       /*
182         ugh. Doesnt work for non-horizontal curves.
183        */
184       Real y = b.curve_point (horizontal[0])[Y_AXIS];
185
186       Real ry = rint (y/staff_space) * staff_space;
187       Real diff = ry - y;
188       Real newy = y;
189       if (fabs (y) <= Staff_symbol_referencer::staff_radius (me)
190           && fabs (diff) < me->paper_l ()->get_var ("tie_staffline_clearance"))
191         {
192           newy = ry - 0.5 * staff_space * sign (diff) ;
193         }
194
195       Real y0 = b.control_ [0][Y_AXIS];
196       b.control_[2][Y_AXIS] = 
197       b.control_[1][Y_AXIS] =
198         (b.control_[1][Y_AXIS] - y0)  * ((newy - y0) / (y - y0)) + y0; 
199     }
200   else
201     programming_error ("Tie is nowhere horizontal");
202
203
204
205   SCM controls = SCM_EOL;
206   for (int i= 4; i--;)
207     controls = gh_cons ( ly_offset2scm (b.control_[i]), controls);
208   return controls;
209 }
210
211 MAKE_SCHEME_CALLBACK(Tie,set_spacing_rods);
212
213 /*
214   TODO: set minimum distances for begin/end of line
215  */
216 SCM
217 Tie::set_spacing_rods (SCM smob)  
218 {
219   Score_element*me = unsmob_element (smob);
220   Spanner*sp = dynamic_cast<Spanner*> (me);
221   Rod r;
222
223   r.item_l_drul_ [LEFT]=sp->get_bound (LEFT);
224   r.item_l_drul_ [RIGHT]=sp->get_bound (RIGHT);  
225   
226   r.distance_f_
227     = gh_scm2double (me->get_elt_property ("minimum-length"))
228     * me->paper_l ()->get_var ("staffspace");
229   r.add_to_cols ();
230   return SCM_UNSPECIFIED;
231 }
232
233 MAKE_SCHEME_CALLBACK(Tie,brew_molecule);
234 SCM
235 Tie::brew_molecule (SCM smob) 
236 {
237   Score_element*me = unsmob_element (smob);
238
239   SCM cp = me->get_elt_property ("control-points");
240   if (cp == SCM_EOL)
241     {
242       cp = get_control_points (smob);
243       me->set_elt_property ("control-points", cp);
244     }
245   
246   Real thick =
247     gh_scm2double (me->get_elt_property ("thickness"))
248     * me->paper_l ()->get_var ("stafflinethickness");
249
250   Bezier b;
251   int i = 0;
252   for (SCM s= cp; s != SCM_EOL; s = gh_cdr (s))
253     {
254       b.control_[i] = ly_scm2offset (gh_car (s));
255       i++;
256     }
257   
258    Molecule a = me->lookup_l ()->slur (b, Directional_element_interface (me).get () * thick, thick);
259    
260    return a.create_scheme ();
261 }
262
263