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 -= max (shift, 0.0);
90 ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
93 ideal = max (ideal, min_desired_space);
94 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
96 Spring ret (max (0.0, ideal), min_dist);
97 ret.set_inverse_compress_strength (max (0.0, ideal - min_desired_space));
98 ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
103 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
105 Real note_head_width = increment;
106 Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
107 Grob *rcolumn = dynamic_cast<Item*> (head)->get_column ();
109 Interval head_extent;
112 head_extent = head->extent (rcolumn, X_AXIS);
114 if (!head_extent.is_empty ())
115 note_head_width = head_extent[RIGHT];
117 note_head_width -= Stem::thickness (right_stem);
120 return -note_head_width * get_grob_direction (right_stem)
121 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
125 different_directions_correction (Grob *note_spacing,
126 Drul_array<Interval> stem_posns,
127 Direction left_stem_dir)
130 Interval intersect = stem_posns[LEFT];
131 intersect.intersect (stem_posns[RIGHT]);
133 if (!intersect.is_empty ())
135 ret = abs (intersect.length ());
140 ret = min (ret / 7, 1.0)
142 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
148 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
151 Correct for the following situation:
160 ^ move the center one to the left.
163 this effect seems to be much more subtle than the
164 stem-direction stuff (why?), and also does not scale with the
165 difference in stem length.
169 Interval hp = head_posns[LEFT];
170 hp.intersect (head_posns[RIGHT]);
175 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
177 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
178 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
180 return (delta > 1) ? -lowest * corr : 0;
185 Correct for optical illusions. See [Wanske] p. 138. The combination
186 up-stem + down-stem should get extra space, the combination
187 down-stem + up-stem less.
189 TODO: have to check whether the stems are in the same staff.
192 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
194 Real *space, Real *fixed)
196 Drul_array<Direction> stem_dirs (CENTER, CENTER);
197 Drul_array<Interval> stem_posns;
198 Drul_array<Interval> head_posns;
199 Drul_array<SCM> props (me->get_object ("left-items"),
200 me->get_object ("right-items"));
202 Drul_array<Spanner *> beams_drul (0, 0);
203 Drul_array<Grob *> stems_drul (0, 0);
205 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
207 Interval bar_xextent;
208 Interval bar_yextent;
212 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
213 rcolumn->break_status_dir (),
215 if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
216 bar_yextent = Staff_spacing::bar_y_positions (bar);
220 vector<Grob*> const &items (ly_scm2link_array (props [d]));
221 for (vsize i = 0; i < items.size (); i++)
223 Item *it = dynamic_cast<Item *> (items[i]);
224 if (!Note_column::has_interface (it))
228 don't correct if accidentals are sticking out of the right side.
230 if (d == RIGHT && Note_column::accidentals (it))
233 Grob *stem = Note_column::get_stem (it);
235 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
238 stems_drul[d] = stem;
239 beams_drul[d] = Stem::get_beam (stem);
241 Direction stem_dir = get_grob_direction (stem);
242 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
245 stem_dirs[d] = stem_dir;
248 Correction doesn't seem appropriate when there is a large flag
249 hanging from the note.
252 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
255 Interval hp = Stem::head_positions (stem);
258 Real chord_start = hp[stem_dir];
261 can't look at stem-end-position, since that triggers
262 beam slope computations.
264 Real stem_end = hp[stem_dir] +
265 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
267 stem_posns[d] = Interval (min (chord_start, stem_end),
268 max (chord_start, stem_end));
269 head_posns[d].unite (hp);
273 while (flip (&d) != LEFT);
275 Real correction = 0.0;
277 if (!bar_yextent.is_empty ())
279 stem_dirs[RIGHT] = -stem_dirs[LEFT];
280 stem_posns[RIGHT] = bar_yextent;
281 stem_posns[RIGHT] *= 2;
284 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
286 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
288 correction = knee_correction (me, stems_drul[RIGHT], increment);
289 *fixed += correction;
293 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
295 if (!bar_yextent.is_empty ())
299 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1)
300 correction = same_direction_correction (me, head_posns);
302 *space += correction;
304 /* there used to be a correction for bar_xextent () here, but
305 it's unclear what that was good for ?
309 ADD_INTERFACE (Note_spacing,
310 "This object calculates spacing wishes for individual voices.",
313 "knee-spacing-correction "
316 "same-direction-correction "
317 "stem-spacing-correction "