]> git.donarmstrong.com Git - lilypond.git/blob - lily/rest-collision.cc
*** empty log message ***
[lilypond.git] / lily / rest-collision.cc
1 /*
2   rest-collision.cc -- implement Rest_collision
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include "rest-collision.hh"
10
11 #include <math.h>               // ceil.
12
13 #include "warn.hh"
14 #include "note-column.hh"
15 #include "stem.hh"
16 #include "rhythmic-head.hh"
17 #include "output-def.hh"
18 #include "rest.hh"
19 #include "group-interface.hh"
20 #include "staff-symbol-referencer.hh"
21 #include "duration.hh"
22 #include "directional-element-interface.hh"
23
24 MAKE_SCHEME_CALLBACK (Rest_collision,force_shift_callback,2);
25 SCM
26 Rest_collision::force_shift_callback (SCM element_smob, SCM axis)
27 {
28   Grob *them = unsmob_grob (element_smob);
29   Axis a = (Axis) scm_to_int (axis);
30   assert (a == Y_AXIS);
31
32   if (Note_column::has_rests (them))
33     {  
34       Grob * rc = unsmob_grob (them->get_property ("rest-collision"));
35
36       if (rc && !to_boolean (rc->get_property ("positioning-done")))
37         {
38           rc->set_property ("positioning-done", SCM_BOOL_T);
39           do_shift (rc);
40         }
41     }  
42   return scm_make_real (0.0);
43 }
44
45
46
47 void
48 Rest_collision::add_column (Grob*me,Grob *p)
49 {
50   me->add_dependency (p);
51   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), p);
52
53   /*
54     only add callback for the rests, since we don't move anything
55     else.
56
57     (not?)
58   */
59   p->add_offset_callback (Rest_collision::force_shift_callback_proc, Y_AXIS);
60   p->set_property ("rest-collision", me->self_scm ());
61 }
62
63
64
65 /*
66   TODO: look at horizontal-shift to determine ordering between rests
67   for more than two voices.
68   
69  */
70 SCM
71 Rest_collision::do_shift (Grob *me)
72 {
73   SCM elts = me->get_property ("elements");
74
75   Link_array<Grob> rests;
76   Link_array<Grob> notes;
77
78   for (SCM s = elts; scm_is_pair (s); s = scm_cdr (s))
79     {
80       Grob * e = unsmob_grob (scm_car (s));
81       if (unsmob_grob (e->get_property ("rest")))
82         {
83           /*
84             Ignore rests under beam.
85            */
86           Grob* st = unsmob_grob (e->get_property ("stem"));
87           if (st && unsmob_grob (st->get_property ("beam")))
88             continue;
89           
90           rests.push (e);
91         }
92       else
93         notes.push (e);
94     }
95
96   
97   /* 
98      handle rest-rest and rest-note collisions
99
100      [todo]
101      * decide not to print rest if too crowded?
102    */
103
104   /*
105     no partners to collide with
106    */
107   if (rests.size () + notes.size () < 2)
108     return SCM_UNSPECIFIED;
109
110
111   Real staff_space = Staff_symbol_referencer::staff_space (me);
112   /*
113     only rests
114   */
115   if (!notes.size ()) 
116     {
117
118       /*
119         This is incomplete: in case of an uneven number of rests, the
120         center one should be centered on the staff.
121        */
122       Drul_array< Link_array <Grob > > ordered_rests;
123       for (int i = 0; i < rests.size (); i++)
124         {
125           Grob * r = Note_column::get_rest (rests[i]);
126           
127           Direction d = get_grob_direction (r);
128           if (d)
129             {
130               ordered_rests[d].push (rests[i]);
131             }
132           else
133             rests[d]->warning (_("rest direction not set.  Cannot resolve collision."));
134         }
135
136       Direction d =  LEFT;
137       do {
138         ordered_rests[d].sort (Note_column::shift_compare);
139       } while (flip (&d) != LEFT);
140       
141       do {
142         if (ordered_rests[d].size () < 1)
143           {
144             if (ordered_rests[-d].size() > 1)
145               ordered_rests[-d][0]->warning (_ ("too many colliding rests"));
146           
147             return SCM_UNSPECIFIED;
148           }
149       } while (flip (&d) != LEFT);
150
151       Grob *common = common_refpoint_of_array (ordered_rests[DOWN], me, Y_AXIS);
152       common =  common_refpoint_of_array (ordered_rests[UP], common, Y_AXIS);
153
154       Real diff = 
155         (ordered_rests[DOWN].top ()->extent (common, Y_AXIS)[UP]
156          - ordered_rests[UP].top ()->extent (common, Y_AXIS)[DOWN]) /staff_space;
157
158       if (diff > 0)
159         {
160           int amount_down = (int) ceil (diff / 2); 
161           diff -= amount_down;
162           Note_column::translate_rests (ordered_rests[DOWN].top (),
163                                         -2 * amount_down);
164           if (diff > 0)
165             Note_column::translate_rests (ordered_rests[UP].top (),
166                                           2 * int (ceil (diff)));
167         }
168
169       do {
170         for (int i = ordered_rests[d].size () -1; i-- > 0;)
171           {
172             Real last_y = ordered_rests[d][i+1]->extent (common, Y_AXIS)[d];
173             Real y = ordered_rests[d][i]->extent (common, Y_AXIS)[-d];
174
175             Real diff = d * ((last_y - y) /staff_space);
176             if (diff > 0)
177               Note_column::translate_rests (ordered_rests[d][i],d * (int) ceil (diff) * 2);
178           }
179       } while (flip (&d) != LEFT);
180     }
181   else 
182     {
183       /*
184         Rests and notes.
185        */
186       if (rests.size () > 1)
187         {
188           warning (_ ("too many colliding rests"));
189         }
190       Grob * rcol = 0;
191       Direction dir = CENTER;
192
193       for (int i = rests.size (); !rcol && i--;)
194         if (Note_column::dir (rests[i]))
195           {
196             dir = Note_column::dir (rests[i]);
197             rcol = rests[i];
198           }
199
200       if (!rcol)
201         return SCM_UNSPECIFIED;
202       
203       Grob *common = common_refpoint_of_array (notes, rcol, Y_AXIS);
204       
205       Interval restdim = rcol->extent (common, Y_AXIS);
206       if (restdim.is_empty ())
207         return SCM_UNSPECIFIED;
208       
209       Real staff_space = Staff_symbol_referencer::staff_space (rcol);
210       Real minimum_dist = robust_scm2double (me->get_property ("minimum-distance"), 1.0) * staff_space;
211
212       Interval notedim;
213       for (int i = 0; i < notes.size (); i++) 
214         {
215           notedim.unite (notes[i]->extent (common, Y_AXIS));
216         }
217
218       Real dist =
219         minimum_dist +  dir * (notedim[dir] - restdim[-dir]) >? 0;
220
221       int stafflines = Staff_symbol_referencer::line_count (me);
222       if (!stafflines)
223         {
224           programming_error ("No staff line count ? ");
225           stafflines =5;
226         }
227       
228       // move discretely by half spaces.
229       int discrete_dist = int (ceil (dist / (0.5 *staff_space)));
230
231       // move by whole spaces inside the staff.
232       if (discrete_dist < stafflines+1)
233         discrete_dist = int (ceil (discrete_dist / 2.0)* 2.0);
234
235       Note_column::translate_rests (rcol,dir * discrete_dist);
236     }
237   return SCM_UNSPECIFIED;
238 }
239
240
241 ADD_INTERFACE (Rest_collision,"rest-collision-interface",
242                "Move around ordinary rests (not multi-measure-rests) to avoid "
243                "conflicts.",
244                "minimum-distance positioning-done elements");
245