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) && !skys[RIGHT].is_empty ())
79 Grob *bar = Pointer_group_interface::find_grob (right_col,
80 ly_symbol2scm ("elements"),
81 Bar_line::has_interface);
85 Real shift = bar->extent (right_col, X_AXIS)[LEFT];
87 min_desired_space -= shift;
91 ideal = max (ideal, min_desired_space);
92 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
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));
101 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
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 ();
107 Interval head_extent;
110 head_extent = head->extent (rcolumn, X_AXIS);
112 if (!head_extent.is_empty ())
113 note_head_width = head_extent[RIGHT];
115 note_head_width -= Stem::thickness (right_stem);
118 return -note_head_width * get_grob_direction (right_stem)
119 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
123 different_directions_correction (Grob *note_spacing,
124 Drul_array<Interval> stem_posns,
125 Direction left_stem_dir)
128 Interval intersect = stem_posns[LEFT];
129 intersect.intersect (stem_posns[RIGHT]);
131 if (!intersect.is_empty ())
133 ret = abs (intersect.length ());
138 ret = min (ret / 7, 1.0)
140 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
146 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
149 Correct for the following situation:
158 ^ move the center one to the left.
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.
167 Interval hp = head_posns[LEFT];
168 hp.intersect (head_posns[RIGHT]);
173 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
175 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
176 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
178 return (delta > 1) ? -lowest * corr : 0;
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.
187 TODO: have to check whether the stems are in the same staff.
190 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
192 Real *space, Real *fixed)
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"));
200 Drul_array<Spanner *> beams_drul (0, 0);
201 Drul_array<Grob *> stems_drul (0, 0);
203 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
205 Interval bar_xextent;
206 Interval bar_yextent;
210 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
211 rcolumn->break_status_dir (),
213 if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
214 bar_yextent = Staff_spacing::bar_y_positions (bar);
218 vector<Grob*> const &items (ly_scm2link_array (props [d]));
219 for (vsize i = 0; i < items.size (); i++)
221 Item *it = dynamic_cast<Item *> (items[i]);
222 if (!Note_column::has_interface (it))
226 don't correct if accidentals are sticking out of the right side.
228 if (d == RIGHT && Note_column::accidentals (it))
231 Grob *stem = Note_column::get_stem (it);
233 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
236 stems_drul[d] = stem;
237 beams_drul[d] = Stem::get_beam (stem);
239 Direction stem_dir = get_grob_direction (stem);
240 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
243 stem_dirs[d] = stem_dir;
246 Correction doesn't seem appropriate when there is a large flag
247 hanging from the note.
250 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
253 Interval hp = Stem::head_positions (stem);
256 Real chord_start = hp[stem_dir];
259 can't look at stem-end-position, since that triggers
260 beam slope computations.
262 Real stem_end = hp[stem_dir] +
263 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
265 stem_posns[d] = Interval (min (chord_start, stem_end),
266 max (chord_start, stem_end));
267 head_posns[d].unite (hp);
271 while (flip (&d) != LEFT);
273 Real correction = 0.0;
275 if (!bar_yextent.is_empty ())
277 stem_dirs[RIGHT] = -stem_dirs[LEFT];
278 stem_posns[RIGHT] = bar_yextent;
279 stem_posns[RIGHT] *= 2;
282 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
284 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
286 correction = knee_correction (me, stems_drul[RIGHT], increment);
287 *fixed += correction;
291 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
293 if (!bar_yextent.is_empty ())
297 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1)
298 correction = same_direction_correction (me, head_posns);
300 *space += correction;
302 /* there used to be a correction for bar_xextent () here, but
303 it's unclear what that was good for ?
307 ADD_INTERFACE (Note_spacing,
308 "This object calculates spacing wishes for individual voices.",
311 "knee-spacing-correction "
314 "same-direction-correction "
315 "stem-spacing-correction "