Ensure scripts inside chords obey manual directions.
[lilypond/mpolesky.git] / lily / note-spacing.cc
blobe12c1cd91e525d93decfb053f26706370204c7a7
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2001--2010 Han-Wen Nienhuys <hanwen@xs4all.nl>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "note-spacing.hh"
22 #include "bar-line.hh"
23 #include "directional-element-interface.hh"
24 #include "grob-array.hh"
25 #include "paper-column.hh"
26 #include "moment.hh"
27 #include "note-column.hh"
28 #include "warn.hh"
29 #include "stem.hh"
30 #include "separation-item.hh"
31 #include "spacing-interface.hh"
32 #include "staff-spacing.hh"
33 #include "accidental-placement.hh"
34 #include "output-def.hh"
35 #include "pointer-group-interface.hh"
37 static bool
38 non_empty_barline (Grob *me)
40 return Bar_line::has_interface (me) && !me->extent (me, X_AXIS).is_empty ();
44 TODO: detect hshifts due to collisions, and account for them in
45 spacing?
48 Spring
49 Note_spacing::get_spacing (Grob *me, Item *right_col,
50 Real base_space, Real increment)
52 vector<Item*> note_columns = Spacing_interface::left_note_columns (me);
53 Real left_head_end = 0;
55 for (vsize i = 0; i < note_columns.size (); i++)
57 SCM r = note_columns[i]->get_object ("rest");
58 Grob *g = unsmob_grob (r);
59 Grob *col = note_columns[i]->get_column ();
61 if (!g)
62 g = Note_column::first_head (note_columns[i]);
65 Ugh. If Stem is switched off, we don't know what the
66 first note head will be.
68 if (g)
70 if (g->common_refpoint (col, X_AXIS) != col)
71 programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
72 else
73 left_head_end = g->extent (col, X_AXIS)[RIGHT];
78 The main factor that determines the amount of space is the width of the
79 note head (or the rest). For example, a quarter rest gets almost 0.5 ss
80 less horizontal space than a note.
82 The other parts of a note column (eg. flags, accidentals, etc.) don't get
83 the full amount of space. We give them half the amount of space, but then
84 adjust things so there are no collisions.
86 Drul_array<Skyline> skys = Spacing_interface::skylines (me, right_col);
87 Real distance = skys[LEFT].distance (skys[RIGHT]);
88 Real min_dist = max (0.0, distance);
89 Real min_desired_space = left_head_end + (min_dist - left_head_end + base_space - increment) / 2;
90 Real ideal = base_space - increment + left_head_end;
92 /* If we have a NonMusical column on the right, we measure the ideal distance
93 to the bar-line (if present), not the start of the column. */
94 if (!Paper_column::is_musical (right_col)
95 && !skys[RIGHT].is_empty ()
96 && to_boolean (me->get_property ("space-to-barline")))
98 Grob *bar = Pointer_group_interface::find_grob (right_col,
99 ly_symbol2scm ("elements"),
100 non_empty_barline);
102 if (bar)
104 Real shift = bar->extent (right_col, X_AXIS)[LEFT];
105 ideal -= shift;
106 min_desired_space -= max (shift, 0.0);
108 else
109 ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
112 ideal = max (ideal, min_desired_space);
113 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
115 /* TODO: grace notes look bad when things are stretched. Should we increase
116 their stretch strength? */
117 Spring ret (max (0.0, ideal), min_dist);
118 ret.set_inverse_compress_strength (max (0.0, ideal - min_desired_space));
119 ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
120 return ret;
123 static Real
124 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
126 Real note_head_width = increment;
127 Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
128 Grob *rcolumn = dynamic_cast<Item*> (head)->get_column ();
130 Interval head_extent;
131 if (head)
133 head_extent = head->extent (rcolumn, X_AXIS);
135 if (!head_extent.is_empty ())
136 note_head_width = head_extent[RIGHT];
138 note_head_width -= Stem::thickness (right_stem);
141 return -note_head_width * get_grob_direction (right_stem)
142 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
145 static Real
146 different_directions_correction (Grob *note_spacing,
147 Drul_array<Interval> stem_posns,
148 Direction left_stem_dir)
150 Real ret = 0.0;
151 Interval intersect = stem_posns[LEFT];
152 intersect.intersect (stem_posns[RIGHT]);
154 if (!intersect.is_empty ())
156 ret = abs (intersect.length ());
159 Ugh. 7 is hardcoded.
161 ret = min (ret / 7, 1.0)
162 * left_stem_dir
163 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
165 return ret;
168 static Real
169 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
172 Correct for the following situation:
177 | X |
178 | | |
179 ========
181 ^ move the center one to the left.
184 this effect seems to be much more subtle than the
185 stem-direction stuff (why?), and also does not scale with the
186 difference in stem length.
190 Interval hp = head_posns[LEFT];
191 hp.intersect (head_posns[RIGHT]);
192 if (!hp.is_empty ())
193 return 0;
195 Direction lowest
196 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
198 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
199 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
201 return (delta > 1) ? -lowest * corr : 0;
206 Correct for optical illusions. See [Wanske] p. 138. The combination
207 up-stem + down-stem should get extra space, the combination
208 down-stem + up-stem less.
210 TODO: have to check whether the stems are in the same staff.
212 void
213 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
214 Real increment,
215 Real *space, Real *fixed)
217 Drul_array<Direction> stem_dirs (CENTER, CENTER);
218 Drul_array<Interval> stem_posns;
219 Drul_array<Interval> head_posns;
220 Drul_array<SCM> props (me->get_object ("left-items"),
221 me->get_object ("right-items"));
223 Drul_array<Spanner *> beams_drul (0, 0);
224 Drul_array<Grob *> stems_drul (0, 0);
226 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
227 Interval intersect;
228 Interval bar_xextent;
229 Interval bar_yextent;
231 Direction d = LEFT;
233 bool acc_right = false;
235 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
236 rcolumn->break_status_dir (),
237 &bar_xextent);
238 if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
239 bar_yextent = Staff_spacing::bar_y_positions (bar);
243 vector<Grob*> const &items (ly_scm2link_array (props [d]));
244 for (vsize i = 0; i < items.size (); i++)
246 Item *it = dynamic_cast<Item *> (items[i]);
247 if (!Note_column::has_interface (it))
248 continue;
249 if (d == RIGHT && it->get_column () != rcolumn)
250 continue;
253 Find accidentals which are sticking out of the right side.
255 if (d == RIGHT)
256 acc_right = acc_right || Note_column::accidentals (it);
258 Grob *stem = Note_column::get_stem (it);
260 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
261 return;
263 stems_drul[d] = stem;
264 beams_drul[d] = Stem::get_beam (stem);
266 Direction stem_dir = get_grob_direction (stem);
267 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
268 return;
270 stem_dirs[d] = stem_dir;
273 Correction doesn't seem appropriate when there is a large flag
274 hanging from the note.
276 if (d == LEFT
277 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
278 return;
280 Interval hp = Stem::head_positions (stem);
281 if (!hp.is_empty ())
283 Real chord_start = hp[stem_dir];
286 can't look at stem-end-position, since that triggers
287 beam slope computations.
289 Real stem_end = hp[stem_dir] +
290 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
292 stem_posns[d] = Interval (min (chord_start, stem_end),
293 max (chord_start, stem_end));
294 head_posns[d].unite (hp);
298 while (flip (&d) != LEFT);
300 Real correction = 0.0;
302 if (!bar_yextent.is_empty ())
304 stem_dirs[RIGHT] = -stem_dirs[LEFT];
305 stem_posns[RIGHT] = bar_yextent;
306 stem_posns[RIGHT] *= 2;
309 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
311 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
313 correction = knee_correction (me, stems_drul[RIGHT], increment);
314 *fixed += correction;
316 else
318 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
320 if (!bar_yextent.is_empty ())
321 correction *= 0.5;
325 Only apply same direction correction if there are no
326 accidentals sticking out of the right hand side.
328 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1
329 && !acc_right)
330 correction = same_direction_correction (me, head_posns);
332 *space += correction;
334 /* there used to be a correction for bar_xextent () here, but
335 it's unclear what that was good for ?
339 ADD_INTERFACE (Note_spacing,
340 "This object calculates spacing wishes for individual voices.",
342 /* properties */
343 "knee-spacing-correction "
344 "left-items "
345 "right-items "
346 "same-direction-correction "
347 "stem-spacing-correction "
348 "space-to-barline "