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