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