2 note-spacing.cc -- implement Note_spacing
4 source file of the GNU LilyPond music typesetter
6 (c) 2001--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 #include "note-spacing.hh"
11 #include "bar-line.hh"
12 #include "directional-element-interface.hh"
13 #include "grob-array.hh"
14 #include "paper-column.hh"
16 #include "note-column.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"
27 TODO: detect hshifts due to collisions, and account for them in
32 Note_spacing::get_spacing (Grob *me, Item *right_col,
33 Real base_space, Real increment)
35 vector<Item*> note_columns = Spacing_interface::left_note_columns (me);
36 Real left_head_end = 0;
38 for (vsize i = 0; i < note_columns.size (); i++)
40 SCM r = note_columns[i]->get_object ("rest");
41 Grob *g = unsmob_grob (r);
42 Grob *col = note_columns[i]->get_column ();
45 g = Note_column::first_head (note_columns[i]);
48 Ugh. If Stem is switched off, we don't know what the
49 first note head will be.
53 if (g->common_refpoint (col, X_AXIS) != col)
54 programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
56 left_head_end = g->extent (col, X_AXIS)[RIGHT];
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.
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.
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;
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)
78 && !skys[RIGHT].is_empty ()
79 && to_boolean (me->get_property ("space-to-barline")))
81 Grob *bar = Pointer_group_interface::find_grob (right_col,
82 ly_symbol2scm ("elements"),
83 Bar_line::has_interface);
87 Real shift = bar->extent (right_col, X_AXIS)[LEFT];
89 min_desired_space -= max (shift, 0.0);
92 ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
95 ideal = max (ideal, min_desired_space);
96 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
98 Spring ret (max (0.0, ideal), min_dist);
99 ret.set_inverse_compress_strength (max (0.0, ideal - min_desired_space));
100 ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
105 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
107 Real note_head_width = increment;
108 Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
109 Grob *rcolumn = dynamic_cast<Item*> (head)->get_column ();
111 Interval head_extent;
114 head_extent = head->extent (rcolumn, X_AXIS);
116 if (!head_extent.is_empty ())
117 note_head_width = head_extent[RIGHT];
119 note_head_width -= Stem::thickness (right_stem);
122 return -note_head_width * get_grob_direction (right_stem)
123 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
127 different_directions_correction (Grob *note_spacing,
128 Drul_array<Interval> stem_posns,
129 Direction left_stem_dir)
132 Interval intersect = stem_posns[LEFT];
133 intersect.intersect (stem_posns[RIGHT]);
135 if (!intersect.is_empty ())
137 ret = abs (intersect.length ());
142 ret = min (ret / 7, 1.0)
144 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
150 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
153 Correct for the following situation:
162 ^ move the center one to the left.
165 this effect seems to be much more subtle than the
166 stem-direction stuff (why?), and also does not scale with the
167 difference in stem length.
171 Interval hp = head_posns[LEFT];
172 hp.intersect (head_posns[RIGHT]);
177 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
179 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
180 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
182 return (delta > 1) ? -lowest * corr : 0;
187 Correct for optical illusions. See [Wanske] p. 138. The combination
188 up-stem + down-stem should get extra space, the combination
189 down-stem + up-stem less.
191 TODO: have to check whether the stems are in the same staff.
194 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
196 Real *space, Real *fixed)
198 Drul_array<Direction> stem_dirs (CENTER, CENTER);
199 Drul_array<Interval> stem_posns;
200 Drul_array<Interval> head_posns;
201 Drul_array<SCM> props (me->get_object ("left-items"),
202 me->get_object ("right-items"));
204 Drul_array<Spanner *> beams_drul (0, 0);
205 Drul_array<Grob *> stems_drul (0, 0);
207 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
209 Interval bar_xextent;
210 Interval bar_yextent;
214 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
215 rcolumn->break_status_dir (),
217 if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
218 bar_yextent = Staff_spacing::bar_y_positions (bar);
222 vector<Grob*> const &items (ly_scm2link_array (props [d]));
223 for (vsize i = 0; i < items.size (); i++)
225 Item *it = dynamic_cast<Item *> (items[i]);
226 if (!Note_column::has_interface (it))
230 don't correct if accidentals are sticking out of the right side.
232 if (d == RIGHT && Note_column::accidentals (it))
235 Grob *stem = Note_column::get_stem (it);
237 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
240 stems_drul[d] = stem;
241 beams_drul[d] = Stem::get_beam (stem);
243 Direction stem_dir = get_grob_direction (stem);
244 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
247 stem_dirs[d] = stem_dir;
250 Correction doesn't seem appropriate when there is a large flag
251 hanging from the note.
254 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
257 Interval hp = Stem::head_positions (stem);
260 Real chord_start = hp[stem_dir];
263 can't look at stem-end-position, since that triggers
264 beam slope computations.
266 Real stem_end = hp[stem_dir] +
267 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
269 stem_posns[d] = Interval (min (chord_start, stem_end),
270 max (chord_start, stem_end));
271 head_posns[d].unite (hp);
275 while (flip (&d) != LEFT);
277 Real correction = 0.0;
279 if (!bar_yextent.is_empty ())
281 stem_dirs[RIGHT] = -stem_dirs[LEFT];
282 stem_posns[RIGHT] = bar_yextent;
283 stem_posns[RIGHT] *= 2;
286 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
288 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
290 correction = knee_correction (me, stems_drul[RIGHT], increment);
291 *fixed += correction;
295 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
297 if (!bar_yextent.is_empty ())
301 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1)
302 correction = same_direction_correction (me, head_posns);
304 *space += correction;
306 /* there used to be a correction for bar_xextent () here, but
307 it's unclear what that was good for ?
311 ADD_INTERFACE (Note_spacing,
312 "This object calculates spacing wishes for individual voices.",
315 "knee-spacing-correction "
318 "same-direction-correction "
319 "stem-spacing-correction "