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