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