]> 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 SCM
105 Tie::get_control_points (SCM smob)
106 {  
107   Spanner*me = unsmob_spanner (smob);
108   Direction headdir = CENTER; 
109   if (head (me,LEFT))
110     headdir = LEFT;
111   else if (head (me,RIGHT))
112     headdir = RIGHT;
113   else
114     {
115       programming_error ("Tie without heads.");
116       me->suicide ();
117       return SCM_UNSPECIFIED;
118     }
119
120   
121   if (!Directional_element_interface::get (me))
122     Directional_element_interface::set (me, Tie::get_default_dir (me));
123   Direction dir = Directional_element_interface::get (me);
124   
125   Real staff_space = Staff_symbol_referencer::staff_space (me);
126
127   Real x_gap_f = gh_scm2double (me->get_grob_property ("x-gap"));
128
129   Grob* l = me->get_bound (LEFT);
130   Grob* r = me->get_bound (RIGHT);  
131
132   Grob* commonx = me->common_refpoint (l, X_AXIS);
133   commonx = me->common_refpoint (r, X_AXIS);
134   
135   Real left_x;
136
137   /*
138      the tie has to be long enough to be visible, but should not go
139     through key sigs. In the 1.5 series the pref.matter - note
140     distance is fixed , so this won't be a problem anymore.
141    */
142   Real lambda = 0.9;            
143   
144   if (Note_head::has_interface (l))
145     {
146       Real where = RIGHT;
147
148       /*
149         This correction is due te the shape of the black note head.
150        */
151       if (Rhythmic_head::duration_log (l) == 2)
152         where += dir* 0.2;
153       left_x = l->extent (l, X_AXIS).linear_combination (where)
154         + x_gap_f;
155     }
156   else
157     left_x = l->extent (l, X_AXIS).linear_combination (lambda);
158   
159
160   Real width;
161   if (Note_head::has_interface (l) && Note_head::has_interface (r))
162     {
163       width = 
164         + r->extent (commonx,X_AXIS)[LEFT]
165         - l->extent (commonx, X_AXIS)[RIGHT]
166         -2 * x_gap_f;
167     }
168   else
169     {
170       if (Note_head::has_interface (l))
171         width = r->relative_coordinate (commonx, X_AXIS)
172           - l->extent (commonx, X_AXIS)[RIGHT]
173           - 2 * x_gap_f;
174       else
175         width =
176           - l->extent (commonx, X_AXIS).linear_combination (lambda)  
177           + r->extent (commonx, X_AXIS)[LEFT]
178           - 2 * x_gap_f;
179     }
180   
181
182
183   SCM details = me->get_grob_property ("details");
184
185   SCM lim // groetjes aan de chirurgendochter.
186     = scm_assq (ly_symbol2scm ("height-limit"),details);
187   
188   Real h_inf = gh_scm2double (ly_cdr (lim)) *  staff_space;
189   Real r_0 = gh_scm2double (ly_cdr (scm_assq (ly_symbol2scm ("ratio"),details)));
190
191   Bezier b  = slur_shape (width, h_inf, r_0);
192   
193   /*
194     I think this better, particularly for small ties. It always allows the user to move ties if
195     they seem in the wrong place
196
197     TODO: what if 2 heads have different size.
198
199   */
200
201   Real ypos = Tie::get_position (me) * staff_space/2
202     + dir * gh_scm2double (me->get_grob_property ("y-offset"));;
203
204   /*
205     Make sure we don't start on a dots
206    */
207   if (Note_head::has_interface (l) && Rhythmic_head::get_dots (l))
208     {
209       Grob* dots = Rhythmic_head::get_dots (l);
210       if(fabs (staff_space * Staff_symbol_referencer::get_position (dots) /2
211                - ypos) < 0.5)
212         {
213           ypos += 0.5 * dir ;
214         }
215     }
216
217   
218   /*
219     todo: prevent ending / staffline collision.
220
221     todo: tie / stem collision
222    */
223
224   b = slur_shape (width,h_inf, r_0);
225   b.scale (1, dir);
226   b.translate (Offset (left_x, ypos));
227   
228
229   /*
230     Avoid colliding of the horizontal part with stafflines.
231
232     
233     TODO: redo this, heuristic is half-baken, and ties often look ugly
234     as a result.
235
236     TODO: doesn't work when on staff with even number of lines.
237    */
238   Array<Real> horizontal (b.solve_derivative (Offset (1,0)));
239   if (horizontal.size ())
240     {
241       /*
242         ugh. Doesnt work for non-horizontal curves.
243        */
244       Real y = b.curve_point (horizontal[0])[Y_AXIS];
245
246       Real ry = rint (y/staff_space) * staff_space;
247       Real diff = ry - y;
248       Real newy = y;
249
250       Real clear = staff_space * gh_scm2double (me->get_grob_property ("staffline-clearance"));
251
252       if (fabs (y) <=
253           Staff_symbol_referencer::staff_radius (me) * staff_space + clear
254           && fabs (diff) < clear)
255         {
256           Real y1 = ry + clear;
257           Real y2 = ry - clear;
258
259           /*
260             ugh, we shove the 0.5 out of our sleeves.
261
262             Any way. This test is to make sure that staffline
263             collision avoidance does not result in completely flat
264             ties.
265            */
266           if (fabs (y1 - ypos) < 0.5)
267             y1 = y2;
268           else if (fabs (y2 - ypos) < 0.5)
269             y2 = y1;
270           
271           newy = (fabs (y1 - y) < fabs (y2 - y)) ? y1 : y2;
272           
273           // newy = ry - 0.5 * staff_space * sign (diff) ;
274
275           /*
276             we don't want horizontal ties
277            */
278           if (fabs (newy - b.control_[0][Y_AXIS]) < 1e-2)
279             {
280               newy = newy + dir * staff_space; 
281             }
282         }
283
284       Real y0 = b.control_ [0][Y_AXIS];
285       b.control_[2][Y_AXIS] = 
286       b.control_[1][Y_AXIS] =
287  (b.control_[1][Y_AXIS] - y0)  * ((newy - y0) / (y - y0)) + y0; 
288     }
289   else
290     programming_error ("Tie is nowhere horizontal");
291
292
293
294   SCM controls = SCM_EOL;
295   for (int i= 4; i--;)
296     controls = gh_cons (ly_offset2scm (b.control_[i]), controls);
297   return controls;
298 }
299
300
301 MAKE_SCHEME_CALLBACK (Tie,brew_molecule,1);
302 SCM
303 Tie::brew_molecule (SCM smob) 
304 {
305   Grob*me = unsmob_grob (smob);
306
307   SCM cp = me->get_grob_property ("control-points");
308   if (cp == SCM_EOL)
309     {
310       cp = get_control_points (smob);
311       me->set_grob_property ("control-points", cp);
312     }
313   
314   Real thick =
315     gh_scm2double (me->get_grob_property ("thickness"))
316     * me->get_paper ()->get_var ("linethickness");
317
318   Bezier b;
319   int i = 0;
320   for (SCM s= cp; s != SCM_EOL; s = ly_cdr (s))
321     {
322       b.control_[i] = ly_scm2offset (ly_car (s));
323       i++;
324     }
325   
326    Molecule a = Lookup::slur (b, Directional_element_interface::get (me) * thick, thick);
327    
328    return a.smobbed_copy ();
329 }
330
331
332
333 ADD_INTERFACE (Tie,"tie-interface",
334   "A tie connecting two noteheads.
335 direction = Forced direction for all ties",
336   "y-offset staffline-clearance control-points heads details thickness x-gap direction minimum-length");