]> git.donarmstrong.com Git - lilypond.git/blob - lily/rest-collision.cc
959bf2156b25338711cab33e5430dcd923a4c4ab
[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--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "rest-collision.hh"
10
11 #include <cmath>                // ceil.
12 using namespace std;
13
14 #include "directional-element-interface.hh"
15 #include "duration.hh"
16 #include "international.hh"
17 #include "note-column.hh"
18 #include "output-def.hh"
19 #include "pointer-group-interface.hh"
20 #include "rest.hh"
21 #include "rhythmic-head.hh"
22 #include "staff-symbol-referencer.hh"
23 #include "stem.hh"
24 #include "warn.hh"
25
26 MAKE_SCHEME_CALLBACK (Rest_collision, force_shift_callback, 1);
27 SCM
28 Rest_collision::force_shift_callback (SCM smob)
29 {
30   Grob *them = unsmob_grob (smob);
31   if (Note_column::has_rests (them))
32     {
33       Grob *collision = unsmob_grob (them->get_object ("rest-collision"));
34
35       if (collision)
36         {
37           (void) collision->get_property ("positioning-done");
38         }
39     }
40   return scm_from_double (0.0);
41 }
42
43 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Rest_collision, force_shift_callback_rest, 2, 1);
44 SCM
45 Rest_collision::force_shift_callback_rest (SCM rest, SCM offset)
46 {
47   Grob *rest_grob = unsmob_grob (rest);
48   Grob *parent = rest_grob->get_parent (X_AXIS);
49
50   /*
51     translate REST; we need the result of this translation later on,
52     while the offset probably still is 0/calculation-in-progress.
53    */
54   if (scm_is_number (offset))
55     rest_grob->translate_axis (scm_to_double (offset), Y_AXIS);
56   
57   if (Note_column::has_interface (parent))
58     force_shift_callback (parent->self_scm ());
59
60   return scm_from_double (0.0);
61 }
62
63 void
64 Rest_collision::add_column (Grob *me, Grob *p)
65 {
66   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), p);
67
68   /*
69     only add callback for the rests, since we don't move anything
70     else.
71
72     (not?)
73   */
74   add_offset_callback (p, Rest_collision::force_shift_callback_proc, Y_AXIS);
75   p->set_object ("rest-collision", me->self_scm ());
76
77   Grob *rest = unsmob_grob (p->get_object ("rest"));
78   if (rest)
79     {
80       chain_offset_callback (rest,
81                              Rest_collision::force_shift_callback_rest_proc, Y_AXIS);
82     }
83 }
84
85 /*
86   TODO: look at horizontal-shift to determine ordering between rests
87   for more than two voices.
88 */
89 MAKE_SCHEME_CALLBACK(Rest_collision, calc_positioning_done, 1);
90 SCM
91 Rest_collision::calc_positioning_done (SCM smob)
92 {
93   Grob *me = unsmob_grob (smob);
94   extract_grob_set (me, "elements", elts);
95
96   vector<Grob*> rests;
97   vector<Grob*> notes;
98
99   for (vsize i = 0; i < elts.size (); i++)
100     {
101       Grob *e = elts[i];
102       if (unsmob_grob (e->get_object ("rest")))
103         {
104           /*
105             Ignore rests under beam.
106           */
107           Grob *st = unsmob_grob (e->get_object ("stem"));
108           if (st && unsmob_grob (st->get_object ("beam")))
109             continue;
110
111           rests.push_back (e);
112         }
113       else
114         notes.push_back (e);
115     }
116
117   /*
118     handle rest-rest and rest-note collisions
119
120     [todo]
121     * decide not to print rest if too crowded?
122     */
123
124   /*
125     no partners to collide with
126   */
127   if (rests.size () + notes.size () < 2)
128     return SCM_UNSPECIFIED;
129
130   Real staff_space = Staff_symbol_referencer::staff_space (me);
131   /*
132     only rests
133   */
134   if (!notes.size ())
135     {
136
137       /*
138         This is incomplete: in case of an uneven number of rests, the
139         center one should be centered on the staff.
140       */
141       Drul_array<vector<Grob*> > ordered_rests;
142       for (vsize i = 0; i < rests.size (); i++)
143         {
144           Grob *r = Note_column::get_rest (rests[i]);
145
146           Direction d = get_grob_direction (r);
147           if (d)
148             ordered_rests[d].push_back (rests[i]);
149           else
150             rests[d]->warning (_ ("can't resolve rest collision: rest direction not set"));
151         }
152
153       Direction d = LEFT;
154       do
155         vector_sort (ordered_rests[d], Note_column::shift_less);
156       while (flip (&d) != LEFT)
157         ;
158
159       do
160         {
161           if (ordered_rests[d].size () < 1)
162             {
163               if (ordered_rests[-d].size () > 1)
164                 ordered_rests[-d][0]->warning (_ ("too many colliding rests"));
165
166               return SCM_UNSPECIFIED;
167             }
168         }
169       while (flip (&d) != LEFT);
170
171       Grob *common = common_refpoint_of_array (ordered_rests[DOWN], me, Y_AXIS);
172       common = common_refpoint_of_array (ordered_rests[UP], common, Y_AXIS);
173
174       Real diff
175         = (ordered_rests[DOWN].back ()->extent (common, Y_AXIS)[UP]
176            - ordered_rests[UP].back ()->extent (common, Y_AXIS)[DOWN]) / staff_space;
177
178       if (diff > 0)
179         {
180           int amount_down = (int) ceil (diff / 2);
181           diff -= amount_down;
182           Note_column::translate_rests (ordered_rests[DOWN].back (),
183                                         -2 * amount_down);
184           if (diff > 0)
185             Note_column::translate_rests (ordered_rests[UP].back (),
186                                           2 * int (ceil (diff)));
187         }
188
189       do
190         {
191           for (vsize i = ordered_rests[d].size () -1; i-- > 0;)
192             {
193               Real last_y = ordered_rests[d][i + 1]->extent (common, Y_AXIS)[d];
194               Real y = ordered_rests[d][i]->extent (common, Y_AXIS)[-d];
195
196               Real diff = d * ((last_y - y) / staff_space);
197               if (diff > 0)
198                 Note_column::translate_rests (ordered_rests[d][i], d * (int) ceil (diff) * 2);
199             }
200         }
201       while (flip (&d) != LEFT);
202     }
203   else
204     {
205       /*
206         Rests and notes.
207       */
208       if (rests.size () > 1)
209         warning (_ ("too many colliding rests"));
210       Grob *rcol = 0;
211       Direction dir = CENTER;
212
213       for (vsize i = rests.size (); !rcol && i--;)
214         if (Note_column::dir (rests[i]))
215           {
216             rcol = rests[i];
217             dir = Note_column::dir (rcol);
218           }
219
220       if (!rcol)
221         return SCM_UNSPECIFIED;
222
223       Grob *rest = Note_column::get_rest (rcol);
224       Grob *common = common_refpoint_of_array (notes, rcol, Y_AXIS);
225
226       Interval restdim = rcol->extent (common, Y_AXIS);
227       if (restdim.is_empty ())
228         return SCM_UNSPECIFIED;
229
230       Real staff_space = Staff_symbol_referencer::staff_space (rcol);
231       Real minimum_dist = robust_scm2double (me->get_property ("minimum-distance"), 1.0) * staff_space;
232
233       Interval notedim;
234       for (vsize i = 0; i < notes.size (); i++)
235         notedim.unite (notes[i]->extent (common, Y_AXIS));
236
237
238       Real y = dir * max (0.0,
239                           -dir * restdim[-dir] + dir * notedim[dir]  + minimum_dist);
240       
241       int stafflines = Staff_symbol_referencer::line_count (me);
242       if (!stafflines)
243         {
244           programming_error ("no staff line count");
245           stafflines = 5;
246         }
247
248       // move discretely by half spaces.
249       int discrete_y = dir * int (ceil (y / (0.5 * dir * staff_space)));
250
251       // move by whole spaces inside the staff.
252       if (fabs (Staff_symbol_referencer::get_position (rest)
253                 + discrete_y) < stafflines + 1)
254         {
255           discrete_y = dir * int (ceil (dir * discrete_y / 2.0) * 2.0);
256         }
257
258       Note_column::translate_rests (rcol, discrete_y);
259     }
260   return SCM_UNSPECIFIED;
261 }
262
263 ADD_INTERFACE (Rest_collision, "rest-collision-interface",
264                "Move around ordinary rests (not multi-measure-rests) to avoid "
265                "conflicts.",
266
267                /* properties */
268                "minimum-distance "
269                "positioning-done "
270                "elements");
271