]> git.donarmstrong.com Git - lilypond.git/blob - lily/rest-collision.cc
Be const correct in Open_type_font::index_to_charcode()
[lilypond.git] / lily / rest-collision.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2010 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "rest-collision.hh"
21
22 #include <cmath>                // ceil.
23 using namespace std;
24
25 #include "directional-element-interface.hh"
26 #include "duration.hh"
27 #include "international.hh"
28 #include "item.hh"
29 #include "note-column.hh"
30 #include "output-def.hh"
31 #include "pointer-group-interface.hh"
32 #include "rest.hh"
33 #include "rhythmic-head.hh"
34 #include "staff-symbol-referencer.hh"
35 #include "stem.hh"
36 #include "grob.hh"
37 #include "warn.hh"
38
39 MAKE_SCHEME_CALLBACK (Rest_collision, force_shift_callback, 1);
40 SCM
41 Rest_collision::force_shift_callback (SCM smob)
42 {
43   Grob *them = unsmob_grob (smob);
44   if (Note_column::has_rests (them))
45     {
46       Grob *collision = unsmob_grob (them->get_object ("rest-collision"));
47
48       if (collision)
49         {
50           (void) collision->get_property ("positioning-done");
51         }
52     }
53   return scm_from_double (0.0);
54 }
55
56 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Rest_collision, force_shift_callback_rest, 2, 1, "");
57 SCM
58 Rest_collision::force_shift_callback_rest (SCM rest, SCM offset)
59 {
60   Grob *rest_grob = unsmob_grob (rest);
61   Grob *parent = rest_grob->get_parent (X_AXIS);
62
63   /*
64     translate REST; we need the result of this translation later on,
65     while the offset probably still is 0/calculation-in-progress.
66    */
67   if (scm_is_number (offset))
68     rest_grob->translate_axis (scm_to_double (offset), Y_AXIS);
69   
70   if (Note_column::has_interface (parent))
71     force_shift_callback (parent->self_scm ());
72
73   return scm_from_double (0.0);
74 }
75
76 void
77 Rest_collision::add_column (Grob *me, Grob *p)
78 {
79   Pointer_group_interface::add_grob (me, ly_symbol2scm ("elements"), p);
80
81   /*
82     only add callback for the rests, since we don't move anything
83     else.
84
85     (not?)
86   */
87   add_offset_callback (p, Rest_collision::force_shift_callback_proc, Y_AXIS);
88   p->set_object ("rest-collision", me->self_scm ());
89
90   Grob *rest = unsmob_grob (p->get_object ("rest"));
91   if (rest)
92     {
93       chain_offset_callback (rest,
94                              Rest_collision::force_shift_callback_rest_proc, Y_AXIS);
95     }
96 }
97
98 /*
99   TODO: look at horizontal-shift to determine ordering between rests
100   for more than two voices.
101 */
102 MAKE_SCHEME_CALLBACK (Rest_collision, calc_positioning_done, 1);
103 SCM
104 Rest_collision::calc_positioning_done (SCM smob)
105 {
106   Grob *me = unsmob_grob (smob);
107
108   me->set_property ("positioning-done", SCM_BOOL_T);
109
110   extract_grob_set (me, "elements", elts);
111
112   vector<Grob*> rests;
113   vector<Grob*> notes;
114
115   for (vsize i = 0; i < elts.size (); i++)
116     {
117       Grob *e = elts[i];
118       if (unsmob_grob (e->get_object ("rest")))
119         rests.push_back (e);
120       else
121         notes.push_back (e);
122     }
123
124   /*
125     handle rest-rest and rest-note collisions
126
127     [todo]
128     * decide not to print rest if too crowded?
129     */
130
131   /*
132     no partners to collide with
133   */
134   if (rests.size () + notes.size () < 2)
135     return SCM_BOOL_T;
136
137   Real staff_space = Staff_symbol_referencer::staff_space (me);
138   /*
139     only rests
140   */
141   if (!notes.size ())
142     {
143
144       /*
145         This is incomplete: in case of an uneven number of rests, the
146         center one should be centered on the staff.
147       */
148       Drul_array<vector<Grob*> > ordered_rests;
149       for (vsize i = 0; i < rests.size (); i++)
150         {
151           Grob *r = Note_column::get_rest (rests[i]);
152
153           Direction d = get_grob_direction (r);
154           if (d)
155             ordered_rests[d].push_back (rests[i]);
156           else
157             rests[d]->warning (_ ("cannot resolve rest collision: rest direction not set"));
158         }
159
160       Direction d = LEFT;
161       do
162         vector_sort (ordered_rests[d], Note_column::shift_less);
163       while (flip (&d) != LEFT)
164         ;
165
166       do
167         {
168           if (ordered_rests[d].size () < 1)
169             {
170               if (ordered_rests[-d].size () > 1)
171                 ordered_rests[-d][0]->warning (_ ("too many colliding rests"));
172
173               return SCM_BOOL_T;
174             }
175         }
176       while (flip (&d) != LEFT);
177
178       Grob *common = common_refpoint_of_array (ordered_rests[DOWN], me, Y_AXIS);
179       common = common_refpoint_of_array (ordered_rests[UP], common, Y_AXIS);
180
181       Real diff
182         = (ordered_rests[DOWN].back ()->extent (common, Y_AXIS)[UP]
183            - ordered_rests[UP].back ()->extent (common, Y_AXIS)[DOWN]) / staff_space;
184
185       if (diff > 0)
186         {
187           int amount_down = (int) ceil (diff / 2);
188           diff -= amount_down;
189           Note_column::translate_rests (ordered_rests[DOWN].back (),
190                                         -2 * amount_down);
191           if (diff > 0)
192             Note_column::translate_rests (ordered_rests[UP].back (),
193                                           2 * int (ceil (diff)));
194         }
195
196       do
197         {
198           for (vsize i = ordered_rests[d].size () -1; i-- > 0;)
199             {
200               Real last_y = ordered_rests[d][i + 1]->extent (common, Y_AXIS)[d];
201               Real y = ordered_rests[d][i]->extent (common, Y_AXIS)[-d];
202
203               Real diff = d * ((last_y - y) / staff_space);
204               if (diff > 0)
205                 Note_column::translate_rests (ordered_rests[d][i], d * (int) ceil (diff) * 2);
206             }
207         }
208       while (flip (&d) != LEFT);
209     }
210   else
211     {
212       /*
213         Rests and notes.
214       */
215       if (rests.size () > 1)
216         warning (_ ("too many colliding rests"));
217       Grob *rcol = 0;
218       Direction dir = CENTER;
219
220       for (vsize i = rests.size (); !rcol && i--;)
221         if (Note_column::dir (rests[i]))
222           {
223             rcol = rests[i];
224             dir = Note_column::dir (rcol);
225           }
226
227       if (!rcol)
228         return SCM_BOOL_T;
229
230       Grob *rest = Note_column::get_rest (rcol);
231       Grob *common = common_refpoint_of_array (notes, rcol, Y_AXIS);
232
233       Interval restdim = rcol->extent (common, Y_AXIS);
234       if (restdim.is_empty ())
235         return SCM_BOOL_T;
236
237       Real staff_space = Staff_symbol_referencer::staff_space (rcol);
238       Real minimum_dist = robust_scm2double (me->get_property ("minimum-distance"), 1.0) * staff_space;
239
240       Interval notedim;
241       for (vsize i = 0; i < notes.size (); i++)
242         {
243           if (Note_column::dir (notes[i]) == -dir
244               // If the note has already happened (but it has a long duration, so there is a collision),
245               // don't look at the stem. If we do, the rest gets shifted down a lot and it looks bad.
246               || dynamic_cast<Item*> (notes[i])->get_column () != dynamic_cast<Item*> (rest)->get_column ())
247             {
248               /* try not to look at the stem, as looking at a beamed
249                  note may trigger beam positioning prematurely.
250
251                  This happens with dotted rests, which need Y
252                  positioning to compute X-positioning.
253               */
254               Grob *head = Note_column::first_head (notes[i]);
255               if (head)
256                 notedim.unite (head->extent (common, Y_AXIS));
257               else
258                 programming_error ("Note_column without first_head()");
259             }
260           else
261             notedim.unite (notes[i]->extent (common, Y_AXIS));
262         }
263
264       Real y = dir * max (0.0,
265                           -dir * restdim[-dir] + dir * notedim[dir]  + minimum_dist);
266       
267       int stafflines = Staff_symbol_referencer::line_count (me);
268       if (!stafflines)
269         {
270           programming_error ("no staff line count");
271           stafflines = 5;
272         }
273
274       // move discretely by half spaces.
275       int discrete_y = dir * int (ceil (y / (0.5 * dir * staff_space)));
276
277       // move by whole spaces inside the staff.
278       if (fabs (Staff_symbol_referencer::get_position (rest)
279                 + discrete_y) < stafflines + 1)
280         {
281           discrete_y = dir * int (ceil (dir * discrete_y / 2.0) * 2.0);
282         }
283
284       Note_column::translate_rests (rcol, discrete_y);
285     }
286   return SCM_BOOL_T;
287 }
288
289 ADD_INTERFACE (Rest_collision,
290                "Move around ordinary rests (not multi-measure-rests) to avoid"
291                " conflicts.",
292
293                /* properties */
294                "minimum-distance "
295                "positioning-done "
296                "elements "
297                );
298