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 "directional-element-interface.hh"
12 #include "grob-array.hh"
13 #include "paper-column.hh"
15 #include "note-column.hh"
18 #include "separation-item.hh"
19 #include "spacing-interface.hh"
20 #include "staff-spacing.hh"
21 #include "accidental-placement.hh"
22 #include "output-def.hh"
23 #include "pointer-group-interface.hh"
26 TODO: detect hshifts due to collisions, and account for them in
31 Note_spacing::get_spacing (Grob *me, Item *right_col,
32 Real base_space, Real increment)
34 vector<Item*> note_columns = Spacing_interface::left_note_columns (me);
35 Real left_head_end = 0;
37 for (vsize i = 0; i < note_columns.size (); i++)
39 SCM r = note_columns[i]->get_object ("rest");
40 Grob *g = unsmob_grob (r);
41 Grob *col = note_columns[i]->get_column ();
44 g = Note_column::first_head (note_columns[i]);
47 Ugh. If Stem is switched off, we don't know what the
48 first note head will be.
52 if (g->common_refpoint (col, X_AXIS) != col)
53 programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
55 left_head_end = g->extent (col, X_AXIS)[RIGHT];
60 The main factor that determines the amount of space is the width of the
61 note head (or the rest). For example, a quarter rest gets almost 0.5 ss
62 less horizontal space than a note.
64 The other parts of a note column (eg. flags, accidentals, etc.) don't get
65 the full amount of space. We give them half the amount of space, but then
66 adjust things so there are no collisions.
68 Drul_array<Skyline> skys = Spacing_interface::skylines (me, right_col);
69 Real distance = skys[LEFT].distance (skys[RIGHT]);
70 Real min_dist = max (0.0, distance);
71 Real min_desired_space = left_head_end + (min_dist - left_head_end) / 2;
72 Real ideal = max (base_space - increment + left_head_end, min_desired_space);
74 /* If the NonMusicalPaperColumn on the right sticks out a lot, ensure that
75 the amount of whitespace between the end of the note-head and that column is
76 (base_spacing - increment) (without this line, this would be the distance,
79 if (!Paper_column::is_musical (right_col))
80 ideal = max (ideal, base_space - increment + distance);
82 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
84 Spring ret (ideal, min_dist);
85 ret.set_inverse_compress_strength (max (0.0, ideal - max (min_dist, min_desired_space)));
86 ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
91 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
93 Real note_head_width = increment;
94 Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
95 Grob *rcolumn = dynamic_cast<Item*> (head)->get_column ();
100 head_extent = head->extent (rcolumn, X_AXIS);
102 if (!head_extent.is_empty ())
103 note_head_width = head_extent[RIGHT];
105 note_head_width -= Stem::thickness (right_stem);
108 return -note_head_width * get_grob_direction (right_stem)
109 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
113 different_directions_correction (Grob *note_spacing,
114 Drul_array<Interval> stem_posns,
115 Direction left_stem_dir)
118 Interval intersect = stem_posns[LEFT];
119 intersect.intersect (stem_posns[RIGHT]);
121 if (!intersect.is_empty ())
123 ret = abs (intersect.length ());
128 ret = min (ret / 7, 1.0)
130 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
136 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
139 Correct for the following situation:
148 ^ move the center one to the left.
151 this effect seems to be much more subtle than the
152 stem-direction stuff (why?), and also does not scale with the
153 difference in stem length.
157 Interval hp = head_posns[LEFT];
158 hp.intersect (head_posns[RIGHT]);
163 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
165 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
166 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
168 return (delta > 1) ? -lowest * corr : 0;
173 Correct for optical illusions. See [Wanske] p. 138. The combination
174 up-stem + down-stem should get extra space, the combination
175 down-stem + up-stem less.
177 TODO: have to check whether the stems are in the same staff.
180 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
182 Real *space, Real *fixed)
184 Drul_array<Direction> stem_dirs (CENTER, CENTER);
185 Drul_array<Interval> stem_posns;
186 Drul_array<Interval> head_posns;
187 Drul_array<SCM> props (me->get_object ("left-items"),
188 me->get_object ("right-items"));
190 Drul_array<Spanner *> beams_drul (0, 0);
191 Drul_array<Grob *> stems_drul (0, 0);
193 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
195 Interval bar_xextent;
196 Interval bar_yextent;
200 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
201 rcolumn->break_status_dir (),
203 if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
204 bar_yextent = Staff_spacing::bar_y_positions (bar);
208 vector<Grob*> const &items (ly_scm2link_array (props [d]));
209 for (vsize i = 0; i < items.size (); i++)
211 Item *it = dynamic_cast<Item *> (items[i]);
214 don't correct if accidentals are sticking out of the right side.
216 if (d == RIGHT && Note_column::accidentals (it))
219 Grob *stem = Note_column::get_stem (it);
221 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
224 stems_drul[d] = stem;
225 beams_drul[d] = Stem::get_beam (stem);
227 Direction stem_dir = get_grob_direction (stem);
228 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
231 stem_dirs[d] = stem_dir;
234 Correction doesn't seem appropriate when there is a large flag
235 hanging from the note.
238 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
241 Interval hp = Stem::head_positions (stem);
244 Real chord_start = hp[stem_dir];
247 can't look at stem-end-position, since that triggers
248 beam slope computations.
250 Real stem_end = hp[stem_dir] +
251 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
253 stem_posns[d] = Interval (min (chord_start, stem_end),
254 max (chord_start, stem_end));
255 head_posns[d].unite (hp);
259 while (flip (&d) != LEFT);
261 Real correction = 0.0;
263 if (!bar_yextent.is_empty ())
265 stem_dirs[RIGHT] = -stem_dirs[LEFT];
266 stem_posns[RIGHT] = bar_yextent;
267 stem_posns[RIGHT] *= 2;
270 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
272 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
274 correction = knee_correction (me, stems_drul[RIGHT], increment);
275 *fixed += correction;
279 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
281 if (!bar_yextent.is_empty ())
285 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1)
286 correction = same_direction_correction (me, head_posns);
288 *space += correction;
290 /* there used to be a correction for bar_xextent () here, but
291 it's unclear what that was good for ?
295 ADD_INTERFACE (Note_spacing,
296 "This object calculates spacing wishes for individual voices.",
299 "knee-spacing-correction "
302 "same-direction-correction "
303 "stem-spacing-correction "