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