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