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);
84 ideal -= bar->extent (right_col, X_AXIS)[LEFT];
87 ideal = max (ideal, min_desired_space);
88 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
90 Spring ret (ideal, min_dist);
91 ret.set_inverse_compress_strength (max (0.0, ideal - min_desired_space));
92 ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
97 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
99 Real note_head_width = increment;
100 Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
101 Grob *rcolumn = dynamic_cast<Item*> (head)->get_column ();
103 Interval head_extent;
106 head_extent = head->extent (rcolumn, X_AXIS);
108 if (!head_extent.is_empty ())
109 note_head_width = head_extent[RIGHT];
111 note_head_width -= Stem::thickness (right_stem);
114 return -note_head_width * get_grob_direction (right_stem)
115 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
119 different_directions_correction (Grob *note_spacing,
120 Drul_array<Interval> stem_posns,
121 Direction left_stem_dir)
124 Interval intersect = stem_posns[LEFT];
125 intersect.intersect (stem_posns[RIGHT]);
127 if (!intersect.is_empty ())
129 ret = abs (intersect.length ());
134 ret = min (ret / 7, 1.0)
136 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
142 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
145 Correct for the following situation:
154 ^ move the center one to the left.
157 this effect seems to be much more subtle than the
158 stem-direction stuff (why?), and also does not scale with the
159 difference in stem length.
163 Interval hp = head_posns[LEFT];
164 hp.intersect (head_posns[RIGHT]);
169 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
171 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
172 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
174 return (delta > 1) ? -lowest * corr : 0;
179 Correct for optical illusions. See [Wanske] p. 138. The combination
180 up-stem + down-stem should get extra space, the combination
181 down-stem + up-stem less.
183 TODO: have to check whether the stems are in the same staff.
186 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
188 Real *space, Real *fixed)
190 Drul_array<Direction> stem_dirs (CENTER, CENTER);
191 Drul_array<Interval> stem_posns;
192 Drul_array<Interval> head_posns;
193 Drul_array<SCM> props (me->get_object ("left-items"),
194 me->get_object ("right-items"));
196 Drul_array<Spanner *> beams_drul (0, 0);
197 Drul_array<Grob *> stems_drul (0, 0);
199 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
201 Interval bar_xextent;
202 Interval bar_yextent;
206 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
207 rcolumn->break_status_dir (),
209 if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
210 bar_yextent = Staff_spacing::bar_y_positions (bar);
214 vector<Grob*> const &items (ly_scm2link_array (props [d]));
215 for (vsize i = 0; i < items.size (); i++)
217 Item *it = dynamic_cast<Item *> (items[i]);
218 if (!Note_column::has_interface (it))
222 don't correct if accidentals are sticking out of the right side.
224 if (d == RIGHT && Note_column::accidentals (it))
227 Grob *stem = Note_column::get_stem (it);
229 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
232 stems_drul[d] = stem;
233 beams_drul[d] = Stem::get_beam (stem);
235 Direction stem_dir = get_grob_direction (stem);
236 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
239 stem_dirs[d] = stem_dir;
242 Correction doesn't seem appropriate when there is a large flag
243 hanging from the note.
246 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
249 Interval hp = Stem::head_positions (stem);
252 Real chord_start = hp[stem_dir];
255 can't look at stem-end-position, since that triggers
256 beam slope computations.
258 Real stem_end = hp[stem_dir] +
259 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
261 stem_posns[d] = Interval (min (chord_start, stem_end),
262 max (chord_start, stem_end));
263 head_posns[d].unite (hp);
267 while (flip (&d) != LEFT);
269 Real correction = 0.0;
271 if (!bar_yextent.is_empty ())
273 stem_dirs[RIGHT] = -stem_dirs[LEFT];
274 stem_posns[RIGHT] = bar_yextent;
275 stem_posns[RIGHT] *= 2;
278 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
280 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
282 correction = knee_correction (me, stems_drul[RIGHT], increment);
283 *fixed += correction;
287 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
289 if (!bar_yextent.is_empty ())
293 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1)
294 correction = same_direction_correction (me, head_posns);
296 *space += correction;
298 /* there used to be a correction for bar_xextent () here, but
299 it's unclear what that was good for ?
303 ADD_INTERFACE (Note_spacing,
304 "This object calculates spacing wishes for individual voices.",
307 "knee-spacing-correction "
310 "same-direction-correction "
311 "stem-spacing-correction "