]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
Update d/copyright
[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 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], robust_scm2double (right_col->get_property ("skyline-vertical-padding"), 0.0));
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   bool acc_right = false;
226
227   Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
228                                                               rcolumn->break_status_dir (),
229                                                               &bar_xextent);
230   if (bar && dynamic_cast<Item *> (bar)->get_column () == rcolumn)
231     bar_yextent = Staff_spacing::bar_y_positions (bar);
232
233   for (LEFT_and_RIGHT (d))
234     {
235       vector<Grob *> const &items (ly_scm2link_array (props [d]));
236       for (vsize i = 0; i < items.size (); i++)
237         {
238           Item *it = dynamic_cast<Item *> (items[i]);
239           if (!Note_column::has_interface (it))
240             continue;
241           if (d == RIGHT && it->get_column () != rcolumn)
242             continue;
243
244           /*
245             Find accidentals which are sticking out of the right side.
246           */
247           if (d == RIGHT)
248             acc_right = acc_right || Note_column::accidentals (it);
249
250           Grob *stem = Note_column::get_stem (it);
251
252           if (!stem || !stem->is_live () || Stem::is_invisible (stem))
253             return;
254
255           stems_drul[d] = stem;
256           beams_drul[d] = Stem::get_beam (stem);
257
258           Direction stem_dir = get_grob_direction (stem);
259           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
260             return;
261
262           stem_dirs[d] = stem_dir;
263
264           /*
265             Correction doesn't seem appropriate  when there is a large flag
266             hanging from the note.
267           */
268           if (d == LEFT
269               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
270             return;
271
272           Interval hp = Stem::head_positions (stem);
273           if (!hp.is_empty ())
274             {
275               Real ss = Staff_symbol_referencer::staff_space (stem);
276               stem_posns[d] = stem->pure_height (stem, 0, INT_MAX) * (2 / ss);
277               head_posns[d].unite (hp);
278             }
279         }
280     }
281
282   Real correction = 0.0;
283
284   if (!bar_yextent.is_empty ())
285     {
286       stem_dirs[RIGHT] = -stem_dirs[LEFT];
287       stem_posns[RIGHT] = bar_yextent;
288       stem_posns[RIGHT] *= 2;
289     }
290
291   if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
292     {
293       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
294         {
295           correction = knee_correction (me, stems_drul[RIGHT], increment);
296         }
297       else
298         {
299           correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
300
301           if (!bar_yextent.is_empty ())
302             correction *= 0.5;
303         }
304     }
305   /*
306     Only apply same direction correction if there are no
307     accidentals sticking out of the right hand side.
308   */
309   else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1
310            && !acc_right)
311     correction = same_direction_correction (me, head_posns);
312
313   *fixed += correction;
314   *space += correction;
315
316   /* there used to be a correction for bar_xextent () here, but
317      it's unclear what that was good for ?
318   */
319 }
320
321 ADD_INTERFACE (Note_spacing,
322                "This object calculates spacing wishes for individual voices.",
323
324                /* properties */
325                "knee-spacing-correction "
326                "left-items "
327                "right-items "
328                "same-direction-correction "
329                "stem-spacing-correction "
330                "space-to-barline "
331               );
332