]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
e59aa6ec841f4660148a4e104759aeebae25afea
[lilypond.git] / lily / note-spacing.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2001--2012  Han-Wen Nienhuys <hanwen@xs4all.nl>
5
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.
10
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.
15
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/>.
18 */
19
20 #include "note-spacing.hh"
21
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 "staff-symbol-referencer.hh"
35 #include "stem.hh"
36 #include "warn.hh"
37
38 /*
39   TODO: detect hshifts due to collisions, and account for them in
40   spacing?
41 */
42 /*
43   Adjust the ideal and minimum distance between note columns,
44   based on the notehead size, skylines, and optical illusions.
45 */
46 Spring
47 Note_spacing::get_spacing (Grob *me, Item *right_col,
48                            Spring base, Real increment)
49 {
50   vector<Item *> note_columns = Spacing_interface::left_note_columns (me);
51   Real left_head_end = 0;
52
53   for (vsize i = 0; i < note_columns.size (); i++)
54     {
55       SCM r = note_columns[i]->get_object ("rest");
56       Grob *g = unsmob_grob (r);
57       Grob *col = note_columns[i]->get_column ();
58
59       if (!g)
60         g = Note_column::first_head (note_columns[i]);
61
62       /*
63         Ugh. If Stem is switched off, we don't know what the
64         first note head will be.
65       */
66       if (g)
67         {
68           if (g->common_refpoint (col, X_AXIS) != col)
69             programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
70           else
71             left_head_end = g->extent (col, X_AXIS)[RIGHT];
72         }
73     }
74
75   /*
76     The main factor that determines the amount of space is the width of the
77     note head (or the rest). For example, a quarter rest gets almost 0.5 ss
78     less horizontal space than a note.
79
80     The other parts of a note column (eg. flags, accidentals, etc.) don't get
81     the full amount of space. We give them half the amount of space, but then
82     adjust things so there are no collisions.
83   */
84   Real ideal = base.distance () - increment + left_head_end;
85   Drul_array<Skyline> skys = Spacing_interface::skylines (me, right_col);
86   Real distance = skys[LEFT].distance (skys[RIGHT], robust_scm2double (right_col->get_property ("skyline-vertical-padding"), 0.0));
87   Real min_dist = max (0.0, distance);
88   Real min_desired_space = (ideal + min_dist) / 2;
89   base.set_min_distance (min_dist);
90
91   /* If we have a NonMusical column on the right, we measure the ideal distance
92      to the bar-line (if present), not the start of the column. */
93   if (!Paper_column::is_musical (right_col)
94       && !skys[RIGHT].is_empty ()
95       && to_boolean (me->get_property ("space-to-barline")))
96     {
97       Grob *bar = Pointer_group_interface::find_grob (right_col,
98                                                       ly_symbol2scm ("elements"),
99                                                       Bar_line::non_empty_barline);
100
101       if (bar)
102         {
103           Real shift = bar->extent (right_col, X_AXIS)[LEFT];
104           ideal -= shift;
105           min_desired_space -= max (shift, 0.0);
106         }
107       else
108         ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
109     }
110
111   ideal = max (ideal, min_desired_space);
112   stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
113
114   base.set_distance (max (0.0, ideal));
115   base.set_inverse_compress_strength (max (0.0, ideal - min_desired_space));
116   return base;
117 }
118
119 static Real
120 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
121 {
122   Real note_head_width = increment;
123   Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
124   Grob *rcolumn = dynamic_cast<Item *> (head)->get_column ();
125
126   Interval head_extent;
127   if (head)
128     {
129       head_extent = head->extent (rcolumn, X_AXIS);
130
131       if (!head_extent.is_empty ())
132         note_head_width = head_extent[RIGHT];
133
134       note_head_width -= Stem::thickness (right_stem);
135     }
136
137   return -note_head_width * get_grob_direction (right_stem)
138          * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
139 }
140
141 static Real
142 different_directions_correction (Grob *note_spacing,
143                                  Drul_array<Interval> stem_posns,
144                                  Direction left_stem_dir)
145 {
146   Real ret = 0.0;
147   Interval intersect = stem_posns[LEFT];
148   intersect.intersect (stem_posns[RIGHT]);
149
150   if (!intersect.is_empty ())
151     {
152       ret = abs (intersect.length ());
153
154       /*
155         Ugh. 7 is hardcoded.
156       */
157       ret = min (ret / 7, 1.0)
158             * left_stem_dir
159             * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
160     }
161   return ret;
162 }
163
164 static Real
165 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
166 {
167   /*
168     Correct for the following situation:
169
170     X      X
171     |      |
172     |      |
173     |   X  |
174     |  |   |
175     ========
176
177     ^ move the center one to the left.
178
179
180     this effect seems to be much more subtle than the
181     stem-direction stuff (why?), and also does not scale with the
182     difference in stem length.
183
184   */
185
186   Interval hp = head_posns[LEFT];
187   hp.intersect (head_posns[RIGHT]);
188   if (!hp.is_empty ())
189     return 0;
190
191   Direction lowest
192     = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
193
194   Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
195   Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
196
197   return (delta > 1) ? -lowest * corr : 0;
198 }
199
200 /*
201   Correct for optical illusions. See [Wanske] p. 138. The combination
202   up-stem + down-stem should get extra space, the combination
203   down-stem + up-stem less.
204
205   TODO: have to check whether the stems are in the same staff.
206 */
207 void
208 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
209                                    Real increment,
210                                    Real *space, Real *fixed)
211 {
212   Drul_array<Direction> stem_dirs (CENTER, CENTER);
213   Drul_array<Interval> stem_posns;
214   Drul_array<Interval> head_posns;
215   Drul_array<SCM> props (me->get_object ("left-items"),
216                          me->get_object ("right-items"));
217
218   Drul_array<Spanner *> beams_drul (0, 0);
219   Drul_array<Grob *> stems_drul (0, 0);
220
221   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
222   Interval intersect;
223   Interval bar_xextent;
224   Interval bar_yextent;
225
226   bool acc_right = false;
227
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);
233
234   for (LEFT_and_RIGHT (d))
235     {
236       vector<Grob *> const &items (ly_scm2link_array (props [d]));
237       for (vsize i = 0; i < items.size (); i++)
238         {
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;
244
245           /*
246             Find accidentals which are sticking out of the right side.
247           */
248           if (d == RIGHT)
249             acc_right = acc_right || Note_column::accidentals (it);
250
251           Grob *stem = Note_column::get_stem (it);
252
253           if (!stem || !stem->is_live () || Stem::is_invisible (stem))
254             return;
255
256           stems_drul[d] = stem;
257           beams_drul[d] = Stem::get_beam (stem);
258
259           Direction stem_dir = get_grob_direction (stem);
260           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
261             return;
262
263           stem_dirs[d] = stem_dir;
264
265           /*
266             Correction doesn't seem appropriate  when there is a large flag
267             hanging from the note.
268           */
269           if (d == LEFT
270               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
271             return;
272
273           Interval hp = Stem::head_positions (stem);
274           if (!hp.is_empty ())
275             {
276               Real ss = Staff_symbol_referencer::staff_space (stem);
277               stem_posns[d] = stem->pure_height (stem, 0, INT_MAX) * (2 / ss);
278               head_posns[d].unite (hp);
279             }
280         }
281     }
282
283   Real correction = 0.0;
284
285   if (!bar_yextent.is_empty ())
286     {
287       stem_dirs[RIGHT] = -stem_dirs[LEFT];
288       stem_posns[RIGHT] = bar_yextent;
289       stem_posns[RIGHT] *= 2;
290     }
291
292   if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
293     {
294       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
295         {
296           correction = knee_correction (me, stems_drul[RIGHT], increment);
297         }
298       else
299         {
300           correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
301
302           if (!bar_yextent.is_empty ())
303             correction *= 0.5;
304         }
305     }
306   /*
307     Only apply same direction correction if there are no
308     accidentals sticking out of the right hand side.
309   */
310   else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1
311            && !acc_right)
312     correction = same_direction_correction (me, head_posns);
313
314   *fixed += correction;
315   *space += correction;
316
317   /* there used to be a correction for bar_xextent () here, but
318      it's unclear what that was good for ?
319   */
320 }
321
322 ADD_INTERFACE (Note_spacing,
323                "This object calculates spacing wishes for individual voices.",
324
325                /* properties */
326                "knee-spacing-correction "
327                "left-items "
328                "right-items "
329                "same-direction-correction "
330                "stem-spacing-correction "
331                "space-to-barline "
332               );
333