]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
Merge branch 'master' into jneeman
[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   Drul_array<Skyline> skys = Spacing_interface::skylines (me, right_col);
69   Real min_dist = max (0.0, skys[LEFT].distance (skys[RIGHT]));
70   Real min_desired_space = left_head_end + (min_dist - left_head_end) / 2;
71
72   /* if the right object sticks out a lot, include a bit of extra space.
73      But only for non-musical-columns; this shouldn't apply to accidentals */
74   if (!Paper_column::is_musical (right_col))
75     min_desired_space = max (min_desired_space,
76                              left_head_end + LEFT * skys[RIGHT].max_height ());
77
78   Real ideal = base_space - increment + min_desired_space;
79
80   stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
81
82   Spring ret (ideal, min_dist);
83   ret.set_inverse_compress_strength (max (0.0, ideal - max (min_dist, min_desired_space)));
84   ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
85   return ret;
86 }
87
88
89 /**
90    Correct for optical illusions. See [Wanske] p. 138. The combination
91    up-stem + down-stem should get extra space, the combination
92    down-stem + up-stem less.
93
94    TODO: have to check whether the stems are in the same staff.
95 */
96 void
97 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
98                                    Real increment,
99                                    Real *space, Real *fixed)
100 {
101   Drul_array<Direction> stem_dirs (CENTER, CENTER);
102   Drul_array<Interval> stem_posns;
103   Drul_array<Interval> head_posns;
104   Drul_array<SCM> props (me->get_object ("left-items"),
105                          me->get_object ("right-items"));
106
107   Drul_array<Spanner *> beams_drul (0, 0);
108   Drul_array<Grob *> stems_drul (0, 0);
109
110   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
111   Interval intersect;
112   Interval bar_xextent;
113   Interval bar_yextent;
114
115   bool correct_stem_dirs = true;
116   Direction d = LEFT;
117   bool acc_right = false;
118
119   do
120     {
121       vector<Grob*> const &items (ly_scm2link_array (props [d]));
122       for (vsize i = 0; i < items.size (); i++)
123         {
124           Item *it = dynamic_cast<Item *> (items[i]);
125
126           if (d == RIGHT)
127             acc_right = acc_right || Note_column::accidentals (it);
128
129           Grob *stem = Note_column::get_stem (it);
130
131           if (!stem || !stem->is_live ())
132             {
133               if (d == RIGHT && Separation_item::has_interface (it))
134                 {
135                   if (it->get_column () != rcolumn)
136                     it = it->find_prebroken_piece (rcolumn->break_status_dir ());
137
138                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
139
140                   if (last)
141                     bar_yextent = Staff_spacing::bar_y_positions (last);
142
143                   break;
144                 }
145
146               return;
147             }
148
149           if (Stem::is_invisible (stem))
150             {
151               correct_stem_dirs = false;
152               continue;
153             }
154
155           stems_drul[d] = stem;
156           beams_drul[d] = Stem::get_beam (stem);
157
158           Direction stem_dir = get_grob_direction (stem);
159           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
160             {
161               correct_stem_dirs = false;
162               continue;
163             }
164           stem_dirs[d] = stem_dir;
165
166           /*
167             Correction doesn't seem appropriate  when there is a large flag
168             hanging from the note.
169           */
170           if (d == LEFT
171               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
172             correct_stem_dirs = false;
173
174           Interval hp = Stem::head_positions (stem);
175           if (correct_stem_dirs
176               && !hp.is_empty ())
177             {
178               Real chord_start = hp[stem_dir];
179
180               /*
181                 can't look at stem-end-position, since that triggers
182                 beam slope computations.
183               */
184               Real stem_end = hp[stem_dir] +
185                 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
186
187               stem_posns[d] = Interval (min (chord_start, stem_end),
188                                         max (chord_start, stem_end));
189               head_posns[d].unite (hp);
190             }
191         }
192     }
193   while (flip (&d) != LEFT);
194
195   /*
196     don't correct if accidentals are sticking out of the right side.
197   */
198   if (acc_right)
199     return;
200
201   Real correction = 0.0;
202
203   if (!bar_yextent.is_empty ())
204     {
205       stem_dirs[RIGHT] = -stem_dirs[LEFT];
206       stem_posns[RIGHT] = bar_yextent;
207       stem_posns[RIGHT] *= 2;
208     }
209
210   if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
211     {
212       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
213         {
214
215           /*
216             this is a knee: maximal correction.
217           */
218           Real note_head_width = increment;
219           Grob *st = stems_drul[RIGHT];
220           Grob *head = st ? Stem::support_head (st) : 0;
221
222           Interval head_extent;
223           if (head)
224             {
225               head_extent = head->extent (rcolumn, X_AXIS);
226
227               if (!head_extent.is_empty ())
228                 note_head_width = head_extent[RIGHT];
229
230               note_head_width -= Stem::thickness (st);
231             }
232
233           correction = note_head_width * stem_dirs[LEFT];
234           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
235           *fixed += correction;
236         }
237       else
238         {
239           intersect = stem_posns[LEFT];
240           intersect.intersect (stem_posns[RIGHT]);
241           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
242
243           if (correct_stem_dirs)
244             {
245               correction = abs (intersect.length ());
246
247               /*
248                 Ugh. 7 is hardcoded.
249               */
250               correction = min (correction / 7, 1.0);
251               correction *= stem_dirs[LEFT];
252               correction
253                 *= robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
254             }
255
256           if (!bar_yextent.is_empty ())
257             correction *= 0.5;
258         }
259     }
260   else if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == UP)
261     {
262       /*
263         Correct for the following situation:
264
265         X      X
266         |      |
267         |      |
268         |   X  |
269         |  |   |
270         ========
271
272         ^ move the center one to the left.
273
274
275         this effect seems to be much more subtle than the
276         stem-direction stuff (why?), and also does not scale with the
277         difference in stem length.
278
279       */
280
281       Interval hp = head_posns[LEFT];
282       hp.intersect (head_posns[RIGHT]);
283       if (!hp.is_empty ())
284         return;
285
286       Direction lowest
287         = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
288
289       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
290       Real corr = robust_scm2double (me->get_property ("same-direction-correction"), 0);
291
292       if (delta > 1)
293         correction = -lowest * corr;
294     }
295
296   *space += correction;
297
298   /* there used to be a correction for bar_xextent () here, but
299      it's unclear what that was good for ?
300   */
301 }
302
303 ADD_INTERFACE (Note_spacing,
304                "This object calculates spacing wishes for individual voices.",
305
306                
307                "knee-spacing-correction "
308                "left-items "
309                "right-items "
310                "same-direction-correction "
311                "stem-spacing-correction "
312
313                );
314