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