]> git.donarmstrong.com Git - lilypond.git/blob - lily/tie.cc
*** empty log message ***
[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--2003 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8 #include <math.h>
9
10
11 #include "spanner.hh"
12 #include "lookup.hh"
13 #include "paper-def.hh"
14 #include "tie.hh"
15 #include "rhythmic-head.hh"
16 #include "bezier.hh"
17 #include "paper-column.hh"
18 #include "warn.hh"
19 #include "staff-symbol-referencer.hh"
20 #include "directional-element-interface.hh"
21 #include "molecule.hh"
22 #include "bezier-bow.hh"
23 #include "stem.hh"
24 #include "note-head.hh"
25
26 /*
27   tie: Connect two noteheads.
28
29   What if we have
30
31   c4 ~ \clef bass ; c4 or
32
33   c4 \staffchange c4
34
35   do we have non-horizontal ties then?
36   */
37
38
39 void
40 Tie::set_head (Grob*me,Direction d, Grob * h)
41 {
42   assert (!head (me,d));
43   index_set_cell (me->get_grob_property ("heads"), d, h->self_scm ());
44   
45   dynamic_cast<Spanner*> (me)->set_bound (d, h);
46   me->add_dependency (h);
47 }
48
49 void
50 Tie::set_interface (Grob*me)
51 {
52   me->set_grob_property ("heads", gh_cons (SCM_EOL, SCM_EOL));
53 }
54
55
56 Grob*
57 Tie::head (Grob*me, Direction d) 
58 {
59   SCM c = me->get_grob_property ("heads");
60   c = index_get_cell (c, d);
61
62   return unsmob_grob (c);
63 }
64
65 Real
66 Tie::get_position (Grob*me) 
67 {
68   Direction d = head (me,LEFT) ? LEFT:RIGHT;
69   return Staff_symbol_referencer::get_position (head (me,d));
70 }
71
72
73 /*
74   Default:  Put the tie oppositie of the stem [Wanske p231]
75
76   In case of chords: Tie_column takes over
77   
78   The direction of the Tie is more complicated (See [Ross] p136 and
79   further).
80 */
81 Direction
82 Tie::get_default_dir (Grob*me) 
83 {
84   Item * sl =  head (me,LEFT) ? Rhythmic_head::get_stem (head (me,LEFT)) :0;
85   Item * sr =  head (me,RIGHT) ? Rhythmic_head::get_stem (head (me,RIGHT)) :0;  
86
87   if (sl && sr)
88     {
89       if (Directional_element_interface::get (sl) == UP
90           && Directional_element_interface::get (sr) == UP)
91         return DOWN;
92     }
93   else if (sl || sr)
94     {
95       Item *s = sl ? sl : sr;
96       return - Directional_element_interface::get (s);
97     }
98
99   
100   return UP;
101 }
102
103 /*
104   TODO: we should also use thickness for computing the clearance
105   between head and tie. Very thick ties will now touch the note head.
106   
107   */
108 SCM
109 Tie::get_control_points (SCM smob)
110 {  
111   Spanner*me = unsmob_spanner (smob);
112   Direction headdir = CENTER; 
113   if (head (me,LEFT))
114     headdir = LEFT;
115   else if (head (me,RIGHT))
116     headdir = RIGHT;
117   else
118     {
119       programming_error ("Tie without heads.");
120       me->suicide ();
121       return SCM_UNSPECIFIED;
122     }
123
124   
125   if (!Directional_element_interface::get (me))
126     Directional_element_interface::set (me, Tie::get_default_dir (me));
127   Direction dir = Directional_element_interface::get (me);
128   
129   Real staff_space = Staff_symbol_referencer::staff_space (me);
130
131   Real x_gap_f = gh_scm2double (me->get_grob_property ("x-gap"));
132
133   Grob* l = me->get_bound (LEFT);
134   Grob* r = me->get_bound (RIGHT);  
135
136   Grob* commonx = me->common_refpoint (l, X_AXIS);
137   commonx = me->common_refpoint (r, X_AXIS);
138   
139   Real left_x;
140
141   /*
142      the tie has to be long enough to be visible, but should not go
143     through key sigs. In the 1.5 series the pref.matter - note
144     distance is fixed , so this won't be a problem anymore.
145    */
146   Real lambda = 0.9;            
147   
148   if (Note_head::has_interface (l))
149     {
150       Real where = RIGHT;
151
152       /*
153         This correction is due te the shape of the black note head.
154        */
155       if (Rhythmic_head::duration_log (l) == 2)
156         where += dir* 0.2;
157       left_x = l->extent (l, X_AXIS).linear_combination (where)
158         + x_gap_f;
159     }
160   else
161     left_x = l->extent (l, X_AXIS).linear_combination (lambda);
162   
163
164   Real width;
165   if (Note_head::has_interface (l) && Note_head::has_interface (r))
166     {
167       width = 
168         + r->extent (commonx,X_AXIS)[LEFT]
169         - l->extent (commonx, X_AXIS)[RIGHT]
170         -2 * x_gap_f;
171     }
172   else
173     {
174       if (Note_head::has_interface (l))
175         width = r->relative_coordinate (commonx, X_AXIS)
176           - l->extent (commonx, X_AXIS)[RIGHT]
177           - 2 * x_gap_f;
178       else
179         width =
180           - l->extent (commonx, X_AXIS).linear_combination (lambda)  
181           + r->extent (commonx, X_AXIS)[LEFT]
182           - 2 * x_gap_f;
183     }
184   
185
186
187   SCM details = me->get_grob_property ("details");
188
189   SCM lim // groetjes aan de chirurgendochter.
190     = scm_assq (ly_symbol2scm ("height-limit"),details);
191   
192   Real h_inf = gh_scm2double (ly_cdr (lim)) *  staff_space;
193   Real r_0 = gh_scm2double (ly_cdr (scm_assq (ly_symbol2scm ("ratio"),details)));
194
195   Bezier b  = slur_shape (width, h_inf, r_0);
196   
197   /*
198     I think this better, particularly for small ties. It always allows the user to move ties if
199     they seem in the wrong place
200
201     TODO: what if 2 heads have different size.
202
203   */
204
205   Real ypos = Tie::get_position (me) * staff_space/2
206     + dir * gh_scm2double (me->get_grob_property ("y-offset"));;
207
208   /*
209     Make sure we don't start on a dots
210    */
211   if (Note_head::has_interface (l) && Rhythmic_head::get_dots (l))
212     {
213       Grob* dots = Rhythmic_head::get_dots (l);
214       if(fabs (staff_space * Staff_symbol_referencer::get_position (dots) /2
215                - ypos) < 0.5)
216         {
217           ypos += 0.5 * dir ;
218         }
219     }
220
221   
222   /*
223     todo: prevent ending / staffline collision.
224
225     todo: tie / stem collision
226    */
227
228   b = slur_shape (width,h_inf, r_0);
229   b.scale (1, dir);
230   b.translate (Offset (left_x, ypos));
231   
232
233   /*
234     Avoid colliding of the horizontal part with stafflines.
235
236     
237     TODO: redo this, heuristic is half-baken, and ties often look ugly
238     as a result.
239
240     TODO: doesn't work when on staff with even number of lines.
241    */
242   Array<Real> horizontal (b.solve_derivative (Offset (1,0)));
243   if (horizontal.size ())
244     {
245       /*
246         ugh. Doesnt work for non-horizontal curves.
247        */
248       Real y = b.curve_point (horizontal[0])[Y_AXIS];
249
250       Real ry = rint (y/staff_space) * staff_space;
251       Real diff = ry - y;
252       Real newy = y;
253
254       Real clear = staff_space * gh_scm2double (me->get_grob_property ("staffline-clearance"));
255
256       if (fabs (y) <=
257           Staff_symbol_referencer::staff_radius (me) * staff_space + clear
258           && fabs (diff) < clear)
259         {
260           Real y1 = ry + clear;
261           Real y2 = ry - clear;
262
263           /*
264             ugh, we shove the 0.5 out of our sleeves.
265
266             Any way. This test is to make sure that staffline
267             collision avoidance does not result in completely flat
268             ties.
269            */
270           if (fabs (y1 - ypos) < 0.5)
271             y1 = y2;
272           else if (fabs (y2 - ypos) < 0.5)
273             y2 = y1;
274           
275           newy = (fabs (y1 - y) < fabs (y2 - y)) ? y1 : y2;
276           
277           // newy = ry - 0.5 * staff_space * sign (diff) ;
278
279           /*
280             we don't want horizontal ties
281            */
282           if (fabs (newy - b.control_[0][Y_AXIS]) < 1e-2)
283             {
284               newy = newy + dir * staff_space; 
285             }
286         }
287
288       Real y0 = b.control_ [0][Y_AXIS];
289       b.control_[2][Y_AXIS] = 
290       b.control_[1][Y_AXIS] =
291  (b.control_[1][Y_AXIS] - y0)  * ((newy - y0) / (y - y0)) + y0; 
292     }
293   else
294     programming_error ("Tie is nowhere horizontal");
295
296
297
298   SCM controls = SCM_EOL;
299   for (int i= 4; i--;)
300     controls = gh_cons (ly_offset2scm (b.control_[i]), controls);
301   return controls;
302 }
303
304
305 MAKE_SCHEME_CALLBACK (Tie,brew_molecule,1);
306 SCM
307 Tie::brew_molecule (SCM smob) 
308 {
309   Grob*me = unsmob_grob (smob);
310
311   SCM cp = me->get_grob_property ("control-points");
312   if (cp == SCM_EOL)
313     {
314       cp = get_control_points (smob);
315       me->set_grob_property ("control-points", cp);
316     }
317   
318   Real thick =
319     gh_scm2double (me->get_grob_property ("thickness"))
320     * me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));
321
322   Bezier b;
323   int i = 0;
324   for (SCM s= cp; s != SCM_EOL; s = ly_cdr (s))
325     {
326       b.control_[i] = ly_scm2offset (ly_car (s));
327       i++;
328     }
329   
330    Molecule a = Lookup::slur (b, Directional_element_interface::get (me) * thick, thick);
331    
332    return a.smobbed_copy ();
333 }
334
335
336
337 ADD_INTERFACE (Tie,"tie-interface",
338   "A tie connecting two noteheads.\n"
339 "direction = Forced direction for all ties",
340   "y-offset staffline-clearance control-points heads details thickness x-gap direction minimum-length");