]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
Grand fixcc.py run on all .hh .cc files.
[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 "stem.hh"
35 #include "warn.hh"
36
37 /*
38   TODO: detect hshifts due to collisions, and account for them in
39   spacing?
40 */
41
42 Spring
43 Note_spacing::get_spacing (Grob *me, Item *right_col,
44                            Real base_space, Real increment)
45 {
46   vector<Item *> note_columns = Spacing_interface::left_note_columns (me);
47   Real left_head_end = 0;
48
49   for (vsize i = 0; i < note_columns.size (); i++)
50     {
51       SCM r = note_columns[i]->get_object ("rest");
52       Grob *g = unsmob_grob (r);
53       Grob *col = note_columns[i]->get_column ();
54
55       if (!g)
56         g = Note_column::first_head (note_columns[i]);
57
58       /*
59         Ugh. If Stem is switched off, we don't know what the
60         first note head will be.
61       */
62       if (g)
63         {
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];
68         }
69     }
70
71   /*
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.
75
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.
79   */
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;
85
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")))
91     {
92       Grob *bar = Pointer_group_interface::find_grob (right_col,
93                                                       ly_symbol2scm ("elements"),
94                                                       Bar_line::non_empty_barline);
95
96       if (bar)
97         {
98           Real shift = bar->extent (right_col, X_AXIS)[LEFT];
99           ideal -= shift;
100           min_desired_space -= max (shift, 0.0);
101         }
102       else
103         ideal -= right_col->extent (right_col, X_AXIS)[RIGHT];
104     }
105
106   ideal = max (ideal, min_desired_space);
107   stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
108
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;
115 }
116
117 static Real
118 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
119 {
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 ();
123
124   Interval head_extent;
125   if (head)
126     {
127       head_extent = head->extent (rcolumn, X_AXIS);
128
129       if (!head_extent.is_empty ())
130         note_head_width = head_extent[RIGHT];
131
132       note_head_width -= Stem::thickness (right_stem);
133     }
134
135   return -note_head_width * get_grob_direction (right_stem)
136          * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
137 }
138
139 static Real
140 different_directions_correction (Grob *note_spacing,
141                                  Drul_array<Interval> stem_posns,
142                                  Direction left_stem_dir)
143 {
144   Real ret = 0.0;
145   Interval intersect = stem_posns[LEFT];
146   intersect.intersect (stem_posns[RIGHT]);
147
148   if (!intersect.is_empty ())
149     {
150       ret = abs (intersect.length ());
151
152       /*
153         Ugh. 7 is hardcoded.
154       */
155       ret = min (ret / 7, 1.0)
156             * left_stem_dir
157             * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
158     }
159   return ret;
160 }
161
162 static Real
163 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
164 {
165   /*
166     Correct for the following situation:
167
168     X      X
169     |      |
170     |      |
171     |   X  |
172     |  |   |
173     ========
174
175     ^ move the center one to the left.
176
177
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.
181
182   */
183
184   Interval hp = head_posns[LEFT];
185   hp.intersect (head_posns[RIGHT]);
186   if (!hp.is_empty ())
187     return 0;
188
189   Direction lowest
190     = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
191
192   Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
193   Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
194
195   return (delta > 1) ? -lowest * corr : 0;
196 }
197
198 /*
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.
202
203   TODO: have to check whether the stems are in the same staff.
204 */
205 void
206 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
207                                    Real increment,
208                                    Real *space, Real *fixed)
209 {
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"));
215
216   Drul_array<Spanner *> beams_drul (0, 0);
217   Drul_array<Grob *> stems_drul (0, 0);
218
219   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
220   Interval intersect;
221   Interval bar_xextent;
222   Interval bar_yextent;
223
224   Direction d = LEFT;
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   do
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 chord_start = hp[stem_dir];
277
278               /*
279                 can't look at stem-end-position, since that triggers
280                 beam slope computations.
281               */
282               Real stem_end = hp[stem_dir]
283                               + stem_dir * robust_scm2double (stem->get_property ("length"), 7);
284
285               stem_posns[d] = Interval (min (chord_start, stem_end),
286                                         max (chord_start, stem_end));
287               head_posns[d].unite (hp);
288             }
289         }
290     }
291   while (flip (&d) != LEFT);
292
293   Real correction = 0.0;
294
295   if (!bar_yextent.is_empty ())
296     {
297       stem_dirs[RIGHT] = -stem_dirs[LEFT];
298       stem_posns[RIGHT] = bar_yextent;
299       stem_posns[RIGHT] *= 2;
300     }
301
302   if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
303     {
304       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
305         {
306           correction = knee_correction (me, stems_drul[RIGHT], increment);
307           *fixed += correction;
308         }
309       else
310         {
311           correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
312
313           if (!bar_yextent.is_empty ())
314             correction *= 0.5;
315         }
316     }
317   /*
318     Only apply same direction correction if there are no
319     accidentals sticking out of the right hand side.
320   */
321   else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1
322            && !acc_right)
323     correction = same_direction_correction (me, head_posns);
324
325   *space += correction;
326
327   /* there used to be a correction for bar_xextent () here, but
328      it's unclear what that was good for ?
329   */
330 }
331
332 ADD_INTERFACE (Note_spacing,
333                "This object calculates spacing wishes for individual voices.",
334
335                /* properties */
336                "knee-spacing-correction "
337                "left-items "
338                "right-items "
339                "same-direction-correction "
340                "stem-spacing-correction "
341                "space-to-barline "
342               );
343