]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
tweaks to spacing and include spring.cc
[lilypond.git] / lily / note-spacing.cc
1 /*
2   note-spacing.cc -- implement Note_spacing
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2001--2007  Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9 #include "note-spacing.hh"
10
11 #include "directional-element-interface.hh"
12 #include "grob-array.hh"
13 #include "paper-column.hh"
14 #include "moment.hh"
15 #include "note-column.hh"
16 #include "warn.hh"
17 #include "stem.hh"
18 #include "separation-item.hh"
19 #include "spacing-interface.hh"
20 #include "staff-spacing.hh"
21 #include "accidental-placement.hh"
22 #include "output-def.hh"
23 #include "pointer-group-interface.hh"
24
25 /*
26   TODO: detect hshifts due to collisions, and account for them in
27   spacing?
28 */
29
30 Spring
31 Note_spacing::get_spacing (Grob *me, Item *right_col,
32                            Real base_space, Real increment)
33 {
34   vector<Item*> note_columns = Spacing_interface::left_note_columns (me);
35   Real left_head_end = 0;
36
37   for (vsize i = 0; i < note_columns.size (); i++)
38     {
39        SCM r = note_columns[i]->get_object ("rest");
40        Grob *g = unsmob_grob (r);
41        Grob *col = note_columns[i]->get_column ();
42
43        if (!g)
44          g = Note_column::first_head (note_columns[i]);
45
46        /*
47          Ugh. If Stem is switched off, we don't know what the
48          first note head will be.
49        */
50        if (g)
51          {
52            if (g->common_refpoint (col, X_AXIS) != col)
53              programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
54            else
55              left_head_end = g->extent (col, X_AXIS)[RIGHT];
56          }
57     }
58
59   /*
60     The main factor that determines the amount of space is the width of the
61     note head (or the rest). For example, a quarter rest gets almost 0.5 ss
62     less horizontal space than a note.
63
64     The other parts of a note column (eg. flags, accidentals, etc.) don't get
65     the full amount of space. We give them half the amount of space, but then
66     adjust things so there are no collisions.
67   */
68   Real min_dist = Spacing_interface::minimum_distance (me, right_col);
69   Real min_desired_space = left_head_end + (min_dist - left_head_end) / 2;
70   Real ideal = base_space - increment + min_desired_space;
71
72   stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
73
74   Spring ret (ideal, min_dist);
75   ret.set_inverse_compress_strength (max (0.0, ideal - max (min_dist, min_desired_space)));
76   ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
77   return ret;
78 }
79
80
81 /**
82    Correct for optical illusions. See [Wanske] p. 138. The combination
83    up-stem + down-stem should get extra space, the combination
84    down-stem + up-stem less.
85
86    TODO: have to check whether the stems are in the same staff.
87 */
88 void
89 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
90                                    Real increment,
91                                    Real *space, Real *fixed)
92 {
93   Drul_array<Direction> stem_dirs (CENTER, CENTER);
94   Drul_array<Interval> stem_posns;
95   Drul_array<Interval> head_posns;
96   Drul_array<SCM> props (me->get_object ("left-items"),
97                          me->get_object ("right-items"));
98
99   Drul_array<Spanner *> beams_drul (0, 0);
100   Drul_array<Grob *> stems_drul (0, 0);
101
102   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
103   Interval intersect;
104   Interval bar_xextent;
105   Interval bar_yextent;
106
107   bool correct_stem_dirs = true;
108   Direction d = LEFT;
109   bool acc_right = false;
110
111   do
112     {
113       vector<Grob*> const &items (ly_scm2link_array (props [d]));
114       for (vsize i = 0; i < items.size (); i++)
115         {
116           Item *it = dynamic_cast<Item *> (items[i]);
117
118           if (d == RIGHT)
119             acc_right = acc_right || Note_column::accidentals (it);
120
121           Grob *stem = Note_column::get_stem (it);
122
123           if (!stem || !stem->is_live ())
124             {
125               if (d == RIGHT && Separation_item::has_interface (it))
126                 {
127                   if (it->get_column () != rcolumn)
128                     it = it->find_prebroken_piece (rcolumn->break_status_dir ());
129
130                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
131
132                   if (last)
133                     bar_yextent = Staff_spacing::bar_y_positions (last);
134
135                   break;
136                 }
137
138               return;
139             }
140
141           if (Stem::is_invisible (stem))
142             {
143               correct_stem_dirs = false;
144               continue;
145             }
146
147           stems_drul[d] = stem;
148           beams_drul[d] = Stem::get_beam (stem);
149
150           Direction stem_dir = get_grob_direction (stem);
151           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
152             {
153               correct_stem_dirs = false;
154               continue;
155             }
156           stem_dirs[d] = stem_dir;
157
158           /*
159             Correction doesn't seem appropriate  when there is a large flag
160             hanging from the note.
161           */
162           if (d == LEFT
163               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
164             correct_stem_dirs = false;
165
166           Interval hp = Stem::head_positions (stem);
167           if (correct_stem_dirs
168               && !hp.is_empty ())
169             {
170               Real chord_start = hp[stem_dir];
171
172               /*
173                 can't look at stem-end-position, since that triggers
174                 beam slope computations.
175               */
176               Real stem_end = hp[stem_dir] +
177                 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
178
179               stem_posns[d] = Interval (min (chord_start, stem_end),
180                                         max (chord_start, stem_end));
181               head_posns[d].unite (hp);
182             }
183         }
184     }
185   while (flip (&d) != LEFT);
186
187   /*
188     don't correct if accidentals are sticking out of the right side.
189   */
190   if (acc_right)
191     return;
192
193   Real correction = 0.0;
194
195   if (!bar_yextent.is_empty ())
196     {
197       stem_dirs[RIGHT] = -stem_dirs[LEFT];
198       stem_posns[RIGHT] = bar_yextent;
199       stem_posns[RIGHT] *= 2;
200     }
201
202   if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
203     {
204       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
205         {
206
207           /*
208             this is a knee: maximal correction.
209           */
210           Real note_head_width = increment;
211           Grob *st = stems_drul[RIGHT];
212           Grob *head = st ? Stem::support_head (st) : 0;
213
214           Interval head_extent;
215           if (head)
216             {
217               head_extent = head->extent (rcolumn, X_AXIS);
218
219               if (!head_extent.is_empty ())
220                 note_head_width = head_extent[RIGHT];
221
222               note_head_width -= Stem::thickness (st);
223             }
224
225           correction = note_head_width * stem_dirs[LEFT];
226           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
227           *fixed += correction;
228         }
229       else
230         {
231           intersect = stem_posns[LEFT];
232           intersect.intersect (stem_posns[RIGHT]);
233           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
234
235           if (correct_stem_dirs)
236             {
237               correction = abs (intersect.length ());
238
239               /*
240                 Ugh. 7 is hardcoded.
241               */
242               correction = min (correction / 7, 1.0);
243               correction *= stem_dirs[LEFT];
244               correction
245                 *= robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
246             }
247
248           if (!bar_yextent.is_empty ())
249             correction *= 0.5;
250         }
251     }
252   else if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == UP)
253     {
254       /*
255         Correct for the following situation:
256
257         X      X
258         |      |
259         |      |
260         |   X  |
261         |  |   |
262         ========
263
264         ^ move the center one to the left.
265
266
267         this effect seems to be much more subtle than the
268         stem-direction stuff (why?), and also does not scale with the
269         difference in stem length.
270
271       */
272
273       Interval hp = head_posns[LEFT];
274       hp.intersect (head_posns[RIGHT]);
275       if (!hp.is_empty ())
276         return;
277
278       Direction lowest
279         = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
280
281       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
282       Real corr = robust_scm2double (me->get_property ("same-direction-correction"), 0);
283
284       if (delta > 1)
285         correction = -lowest * corr;
286     }
287
288   *space += correction;
289
290   /* there used to be a correction for bar_xextent () here, but
291      it's unclear what that was good for ?
292   */
293 }
294
295 ADD_INTERFACE (Note_spacing,
296                "This object calculates spacing wishes for individual voices.",
297
298                
299                "knee-spacing-correction "
300                "left-items "
301                "right-items "
302                "same-direction-correction "
303                "stem-spacing-correction "
304
305                );
306