Use scalar instead of embedded_scm for context mod overrides.
[lilypond/mpolesky.git] / lily / note-spacing.cc
blob81cb8c2fcd52fd51bed952166124de5a4e659693
1 /*
2 note-spacing.cc -- implement Note_spacing
4 source file of the GNU LilyPond music typesetter
6 (c) 2001--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
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"
15 #include "moment.hh"
16 #include "note-column.hh"
17 #include "warn.hh"
18 #include "stem.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"
26 static bool
27 non_empty_barline (Grob *me)
29 return Bar_line::has_interface (me) && !me->extent (me, X_AXIS).is_empty ();
33 TODO: detect hshifts due to collisions, and account for them in
34 spacing?
37 Spring
38 Note_spacing::get_spacing (Grob *me, Item *right_col,
39 Real base_space, Real increment)
41 vector<Item*> note_columns = Spacing_interface::left_note_columns (me);
42 Real left_head_end = 0;
44 for (vsize i = 0; i < note_columns.size (); i++)
46 SCM r = note_columns[i]->get_object ("rest");
47 Grob *g = unsmob_grob (r);
48 Grob *col = note_columns[i]->get_column ();
50 if (!g)
51 g = Note_column::first_head (note_columns[i]);
54 Ugh. If Stem is switched off, we don't know what the
55 first note head will be.
57 if (g)
59 if (g->common_refpoint (col, X_AXIS) != col)
60 programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
61 else
62 left_head_end = g->extent (col, X_AXIS)[RIGHT];
67 The main factor that determines the amount of space is the width of the
68 note head (or the rest). For example, a quarter rest gets almost 0.5 ss
69 less horizontal space than a note.
71 The other parts of a note column (eg. flags, accidentals, etc.) don't get
72 the full amount of space. We give them half the amount of space, but then
73 adjust things so there are no collisions.
75 Drul_array<Skyline> skys = Spacing_interface::skylines (me, right_col);
76 Real distance = skys[LEFT].distance (skys[RIGHT]);
77 Real min_dist = max (0.0, distance);
78 Real min_desired_space = left_head_end + (min_dist - left_head_end + base_space - increment) / 2;
79 Real ideal = base_space - increment + left_head_end;
81 /* If we have a NonMusical column on the right, we measure the ideal distance
82 to the bar-line (if present), not the start of the column. */
83 if (!Paper_column::is_musical (right_col)
84 && !skys[RIGHT].is_empty ()
85 && to_boolean (me->get_property ("space-to-barline")))
87 Grob *bar = Pointer_group_interface::find_grob (right_col,
88 ly_symbol2scm ("elements"),
89 non_empty_barline);
91 if (bar)
93 Real shift = bar->extent (right_col, X_AXIS)[LEFT];
94 ideal -= shift;
95 min_desired_space -= max (shift, 0.0);
97 else
98 ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
101 ideal = max (ideal, min_desired_space);
102 stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
104 /* TODO: grace notes look bad when things are stretched. Should we increase
105 their stretch strength? */
106 Spring ret (max (0.0, ideal), min_dist);
107 ret.set_inverse_compress_strength (max (0.0, ideal - min_desired_space));
108 ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
109 return ret;
112 static Real
113 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
115 Real note_head_width = increment;
116 Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
117 Grob *rcolumn = dynamic_cast<Item*> (head)->get_column ();
119 Interval head_extent;
120 if (head)
122 head_extent = head->extent (rcolumn, X_AXIS);
124 if (!head_extent.is_empty ())
125 note_head_width = head_extent[RIGHT];
127 note_head_width -= Stem::thickness (right_stem);
130 return -note_head_width * get_grob_direction (right_stem)
131 * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
134 static Real
135 different_directions_correction (Grob *note_spacing,
136 Drul_array<Interval> stem_posns,
137 Direction left_stem_dir)
139 Real ret = 0.0;
140 Interval intersect = stem_posns[LEFT];
141 intersect.intersect (stem_posns[RIGHT]);
143 if (!intersect.is_empty ())
145 ret = abs (intersect.length ());
148 Ugh. 7 is hardcoded.
150 ret = min (ret / 7, 1.0)
151 * left_stem_dir
152 * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
154 return ret;
157 static Real
158 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
161 Correct for the following situation:
166 | X |
167 | | |
168 ========
170 ^ move the center one to the left.
173 this effect seems to be much more subtle than the
174 stem-direction stuff (why?), and also does not scale with the
175 difference in stem length.
179 Interval hp = head_posns[LEFT];
180 hp.intersect (head_posns[RIGHT]);
181 if (!hp.is_empty ())
182 return 0;
184 Direction lowest
185 = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
187 Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
188 Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
190 return (delta > 1) ? -lowest * corr : 0;
195 Correct for optical illusions. See [Wanske] p. 138. The combination
196 up-stem + down-stem should get extra space, the combination
197 down-stem + up-stem less.
199 TODO: have to check whether the stems are in the same staff.
201 void
202 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
203 Real increment,
204 Real *space, Real *fixed)
206 Drul_array<Direction> stem_dirs (CENTER, CENTER);
207 Drul_array<Interval> stem_posns;
208 Drul_array<Interval> head_posns;
209 Drul_array<SCM> props (me->get_object ("left-items"),
210 me->get_object ("right-items"));
212 Drul_array<Spanner *> beams_drul (0, 0);
213 Drul_array<Grob *> stems_drul (0, 0);
215 stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
216 Interval intersect;
217 Interval bar_xextent;
218 Interval bar_yextent;
220 Direction d = LEFT;
222 bool acc_right = false;
224 Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
225 rcolumn->break_status_dir (),
226 &bar_xextent);
227 if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
228 bar_yextent = Staff_spacing::bar_y_positions (bar);
232 vector<Grob*> const &items (ly_scm2link_array (props [d]));
233 for (vsize i = 0; i < items.size (); i++)
235 Item *it = dynamic_cast<Item *> (items[i]);
236 if (!Note_column::has_interface (it))
237 continue;
238 if (d == RIGHT && it->get_column () != rcolumn)
239 continue;
242 Find accidentals which are sticking out of the right side.
244 if (d == RIGHT)
245 acc_right = acc_right || Note_column::accidentals (it);
247 Grob *stem = Note_column::get_stem (it);
249 if (!stem || !stem->is_live () || Stem::is_invisible (stem))
250 return;
252 stems_drul[d] = stem;
253 beams_drul[d] = Stem::get_beam (stem);
255 Direction stem_dir = get_grob_direction (stem);
256 if (stem_dirs[d] && stem_dirs[d] != stem_dir)
257 return;
259 stem_dirs[d] = stem_dir;
262 Correction doesn't seem appropriate when there is a large flag
263 hanging from the note.
265 if (d == LEFT
266 && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
267 return;
269 Interval hp = Stem::head_positions (stem);
270 if (!hp.is_empty ())
272 Real chord_start = hp[stem_dir];
275 can't look at stem-end-position, since that triggers
276 beam slope computations.
278 Real stem_end = hp[stem_dir] +
279 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
281 stem_posns[d] = Interval (min (chord_start, stem_end),
282 max (chord_start, stem_end));
283 head_posns[d].unite (hp);
287 while (flip (&d) != LEFT);
289 Real correction = 0.0;
291 if (!bar_yextent.is_empty ())
293 stem_dirs[RIGHT] = -stem_dirs[LEFT];
294 stem_posns[RIGHT] = bar_yextent;
295 stem_posns[RIGHT] *= 2;
298 if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
300 if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
302 correction = knee_correction (me, stems_drul[RIGHT], increment);
303 *fixed += correction;
305 else
307 correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
309 if (!bar_yextent.is_empty ())
310 correction *= 0.5;
314 Only apply same direction correction if there are no
315 accidentals sticking out of the right hand side.
317 else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1
318 && !acc_right)
319 correction = same_direction_correction (me, head_posns);
321 *space += correction;
323 /* there used to be a correction for bar_xextent () here, but
324 it's unclear what that was good for ?
328 ADD_INTERFACE (Note_spacing,
329 "This object calculates spacing wishes for individual voices.",
331 /* properties */
332 "knee-spacing-correction "
333 "left-items "
334 "right-items "
335 "same-direction-correction "
336 "stem-spacing-correction "
337 "space-to-barline "