Fixes Issue 1504, allowing feather beam line breaking.
[lilypond/patrick.git] / lily / note-spacing.cc
blob73faa02100bd0ea2782c2f4cc5e6e0a2c34ac8c2
1 /*
2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2001--2011 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 "accidental-placement.hh"
23 #include "bar-line.hh"
24 #include "directional-element-interface.hh"
25 #include "grob-array.hh"
26 #include "moment.hh"
27 #include "note-column.hh"
28 #include "output-def.hh"
29 #include "paper-column.hh"
30 #include "pointer-group-interface.hh"
31 #include "separation-item.hh"
32 #include "spacing-interface.hh"
33 #include "staff-spacing.hh"
34 #include "stem.hh"
35 #include "warn.hh"
38 TODO: detect hshifts due to collisions, and account for them in
39 spacing?
42 Spring
43 Note_spacing::get_spacing (Grob *me, Item *right_col,
44 Real base_space, Real increment)
46 vector<Item *> note_columns = Spacing_interface::left_note_columns (me);
47 Real left_head_end = 0;
49 for (vsize i = 0; i < note_columns.size (); i++)
51 SCM r = note_columns[i]->get_object ("rest");
52 Grob *g = unsmob_grob (r);
53 Grob *col = note_columns[i]->get_column ();
55 if (!g)
56 g = Note_column::first_head (note_columns[i]);
59 Ugh. If Stem is switched off, we don't know what the
60 first note head will be.
62 if (g)
64 if (g->common_refpoint (col, X_AXIS) != col)
65 programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
66 else
67 left_head_end = g->extent (col, X_AXIS)[RIGHT];
72 The main factor that determines the amount of space is the width of the
73 note head (or the rest). For example, a quarter rest gets almost 0.5 ss
74 less horizontal space than a note.
76 The other parts of a note column (eg. flags, accidentals, etc.) don't get
77 the full amount of space. We give them half the amount of space, but then
78 adjust things so there are no collisions.
80 Drul_array<Skyline> skys = Spacing_interface::skylines (me, right_col);
81 Real distance = skys[LEFT].distance (skys[RIGHT]);
82 Real min_dist = max (0.0, distance);
83 Real min_desired_space = left_head_end + (min_dist - left_head_end + base_space - increment) / 2;
84 Real ideal = base_space - increment + left_head_end;
86 /* If we have a NonMusical column on the right, we measure the ideal distance
87 to the bar-line (if present), not the start of the column. */
88 if (!Paper_column::is_musical (right_col)
89 && !skys[RIGHT].is_empty ()
90 && to_boolean (me->get_property ("space-to-barline")))
92 Grob *bar = Pointer_group_interface::find_grob (right_col,
93 ly_symbol2scm ("elements"),
94 Bar_line::non_empty_barline);
96 if (bar)
98 Real shift = bar->extent (right_col, X_AXIS)[LEFT];
99 ideal -= shift;
100 min_desired_space -= max (shift, 0.0);
102 else
103 ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
106 ideal = max (ideal, min_desired_space);
107 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
109 /* TODO: grace notes look bad when things are stretched. Should we increase
110 their stretch strength? */
111 Spring ret (max (0.0, ideal), min_dist);
112 ret.set_inverse_compress_strength (max (0.0, ideal - min_desired_space));
113 ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
114 return ret;
117 static Real
118 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
120 Real note_head_width = increment;
121 Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
122 Grob *rcolumn = dynamic_cast<Item *> (head)->get_column ();
124 Interval head_extent;
125 if (head)
127 head_extent = head->extent (rcolumn, X_AXIS);
129 if (!head_extent.is_empty ())
130 note_head_width = head_extent[RIGHT];
132 note_head_width -= Stem::thickness (right_stem);
135 return -note_head_width * get_grob_direction (right_stem)
136 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
139 static Real
140 different_directions_correction (Grob *note_spacing,
141 Drul_array<Interval> stem_posns,
142 Direction left_stem_dir)
144 Real ret = 0.0;
145 Interval intersect = stem_posns[LEFT];
146 intersect.intersect (stem_posns[RIGHT]);
148 if (!intersect.is_empty ())
150 ret = abs (intersect.length ());
153 Ugh. 7 is hardcoded.
155 ret = min (ret / 7, 1.0)
156 * left_stem_dir
157 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
159 return ret;
162 static Real
163 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
166 Correct for the following situation:
171 | X |
172 | | |
173 ========
175 ^ move the center one to the left.
178 this effect seems to be much more subtle than the
179 stem-direction stuff (why?), and also does not scale with the
180 difference in stem length.
184 Interval hp = head_posns[LEFT];
185 hp.intersect (head_posns[RIGHT]);
186 if (!hp.is_empty ())
187 return 0;
189 Direction lowest
190 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
192 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
193 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
195 return (delta > 1) ? -lowest * corr : 0;
199 Correct for optical illusions. See [Wanske] p. 138. The combination
200 up-stem + down-stem should get extra space, the combination
201 down-stem + up-stem less.
203 TODO: have to check whether the stems are in the same staff.
205 void
206 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
207 Real increment,
208 Real *space, Real *fixed)
210 Drul_array<Direction> stem_dirs (CENTER, CENTER);
211 Drul_array<Interval> stem_posns;
212 Drul_array<Interval> head_posns;
213 Drul_array<SCM> props (me->get_object ("left-items"),
214 me->get_object ("right-items"));
216 Drul_array<Spanner *> beams_drul (0, 0);
217 Drul_array<Grob *> stems_drul (0, 0);
219 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
220 Interval intersect;
221 Interval bar_xextent;
222 Interval bar_yextent;
224 Direction d = LEFT;
226 bool acc_right = false;
228 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
229 rcolumn->break_status_dir (),
230 &bar_xextent);
231 if (bar && dynamic_cast<Item *> (bar)->get_column () == rcolumn)
232 bar_yextent = Staff_spacing::bar_y_positions (bar);
236 vector<Grob *> const &items (ly_scm2link_array (props [d]));
237 for (vsize i = 0; i < items.size (); i++)
239 Item *it = dynamic_cast<Item *> (items[i]);
240 if (!Note_column::has_interface (it))
241 continue;
242 if (d == RIGHT && it->get_column () != rcolumn)
243 continue;
246 Find accidentals which are sticking out of the right side.
248 if (d == RIGHT)
249 acc_right = acc_right || Note_column::accidentals (it);
251 Grob *stem = Note_column::get_stem (it);
253 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
254 return;
256 stems_drul[d] = stem;
257 beams_drul[d] = Stem::get_beam (stem);
259 Direction stem_dir = get_grob_direction (stem);
260 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
261 return;
263 stem_dirs[d] = stem_dir;
266 Correction doesn't seem appropriate when there is a large flag
267 hanging from the note.
269 if (d == LEFT
270 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
271 return;
273 Interval hp = Stem::head_positions (stem);
274 if (!hp.is_empty ())
276 Real chord_start = hp[stem_dir];
279 can't look at stem-end-position, since that triggers
280 beam slope computations.
282 Real stem_end = hp[stem_dir]
283 + stem_dir * robust_scm2double (stem->get_property ("length"), 7);
285 stem_posns[d] = Interval (min (chord_start, stem_end),
286 max (chord_start, stem_end));
287 head_posns[d].unite (hp);
291 while (flip (&d) != LEFT);
293 Real correction = 0.0;
295 if (!bar_yextent.is_empty ())
297 stem_dirs[RIGHT] = -stem_dirs[LEFT];
298 stem_posns[RIGHT] = bar_yextent;
299 stem_posns[RIGHT] *= 2;
302 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
304 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
306 correction = knee_correction (me, stems_drul[RIGHT], increment);
307 *fixed += correction;
309 else
311 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
313 if (!bar_yextent.is_empty ())
314 correction *= 0.5;
318 Only apply same direction correction if there are no
319 accidentals sticking out of the right hand side.
321 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1
322 && !acc_right)
323 correction = same_direction_correction (me, head_posns);
325 *space += correction;
327 /* there used to be a correction for bar_xextent () here, but
328 it's unclear what that was good for ?
332 ADD_INTERFACE (Note_spacing,
333 "This object calculates spacing wishes for individual voices.",
335 /* properties */
336 "knee-spacing-correction "
337 "left-items "
338 "right-items "
339 "same-direction-correction "
340 "stem-spacing-correction "
341 "space-to-barline "