]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
Tweaks to Note_spacing.
[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 distance = skys[LEFT].distance (skys[RIGHT]);
70   Real min_dist = max (0.0, distance);
71   Real min_desired_space = left_head_end + (min_dist - left_head_end) / 2;
72   Real ideal = max (base_space - increment + left_head_end, min_desired_space);
73
74   /* If the NonMusicalPaperColumn on the right sticks out a lot, ensure that
75      the amount of whitespace between the end of the note-head and that column is
76      (base_spacing - increment) (without this line, this would be the distance,
77      not the whitespace)
78   */
79   if (!Paper_column::is_musical (right_col))
80     ideal = max (ideal, base_space - increment + distance);
81
82   stem_dir_correction (me, right_col, increment, &ideal, &min_desired_space);
83
84   Spring ret (ideal, min_dist);
85   ret.set_inverse_compress_strength (max (0.0, ideal - max (min_dist, min_desired_space)));
86   ret.set_inverse_stretch_strength (max (0.1, base_space - increment));
87   return ret;
88 }
89
90 static Real
91 knee_correction (Grob *note_spacing, Grob *right_stem, Real increment)
92 {
93   Real note_head_width = increment;
94   Grob *head = right_stem ? Stem::support_head (right_stem) : 0;
95   Grob *rcolumn = dynamic_cast<Item*> (head)->get_column ();
96
97   Interval head_extent;
98   if (head)
99     {
100       head_extent = head->extent (rcolumn, X_AXIS);
101
102       if (!head_extent.is_empty ())
103         note_head_width = head_extent[RIGHT];
104
105       note_head_width -= Stem::thickness (right_stem);
106     }
107
108   return -note_head_width * get_grob_direction (right_stem)
109     * robust_scm2double (note_spacing->get_property ("knee-spacing-correction"), 0);
110 }
111
112 static Real
113 different_directions_correction (Grob *note_spacing,
114                                  Drul_array<Interval> stem_posns,
115                                  Direction left_stem_dir)
116 {
117   Real ret = 0.0;
118   Interval intersect = stem_posns[LEFT];
119   intersect.intersect (stem_posns[RIGHT]);
120
121   if (!intersect.is_empty ())
122     {
123       ret = abs (intersect.length ());
124
125       /*
126         Ugh. 7 is hardcoded.
127       */
128       ret = min (ret / 7, 1.0)
129         * left_stem_dir
130         * robust_scm2double (note_spacing->get_property ("stem-spacing-correction"), 0);
131     }
132   return ret;
133 }
134
135 static Real
136 same_direction_correction (Grob *note_spacing, Drul_array<Interval> head_posns)
137 {
138   /*
139     Correct for the following situation:
140     
141     X      X
142     |      |
143     |      |
144     |   X  |
145     |  |   |
146     ========
147     
148     ^ move the center one to the left.
149     
150     
151     this effect seems to be much more subtle than the
152     stem-direction stuff (why?), and also does not scale with the
153     difference in stem length.
154     
155   */
156
157   Interval hp = head_posns[LEFT];
158   hp.intersect (head_posns[RIGHT]);
159   if (!hp.is_empty ())
160     return 0;
161   
162   Direction lowest
163     = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
164   
165   Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
166   Real corr = robust_scm2double (note_spacing->get_property ("same-direction-correction"), 0);
167   
168   return (delta > 1) ? -lowest * corr : 0;
169 }
170
171
172 /**
173    Correct for optical illusions. See [Wanske] p. 138. The combination
174    up-stem + down-stem should get extra space, the combination
175    down-stem + up-stem less.
176
177    TODO: have to check whether the stems are in the same staff.
178 */
179 void
180 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
181                                    Real increment,
182                                    Real *space, Real *fixed)
183 {
184   Drul_array<Direction> stem_dirs (CENTER, CENTER);
185   Drul_array<Interval> stem_posns;
186   Drul_array<Interval> head_posns;
187   Drul_array<SCM> props (me->get_object ("left-items"),
188                          me->get_object ("right-items"));
189
190   Drul_array<Spanner *> beams_drul (0, 0);
191   Drul_array<Grob *> stems_drul (0, 0);
192
193   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
194   Interval intersect;
195   Interval bar_xextent;
196   Interval bar_yextent;
197
198   Direction d = LEFT;
199
200   Grob *bar = Spacing_interface::extremal_break_aligned_grob (me, RIGHT,
201                                                               rcolumn->break_status_dir (),
202                                                               &bar_xextent);
203   if (bar && dynamic_cast<Item*> (bar)->get_column () == rcolumn)
204     bar_yextent = Staff_spacing::bar_y_positions (bar);
205
206   do
207     {
208       vector<Grob*> const &items (ly_scm2link_array (props [d]));
209       for (vsize i = 0; i < items.size (); i++)
210         {
211           Item *it = dynamic_cast<Item *> (items[i]);
212
213           /*
214             don't correct if accidentals are sticking out of the right side.
215           */
216           if (d == RIGHT && Note_column::accidentals (it))
217             return;
218
219           Grob *stem = Note_column::get_stem (it);
220
221           if (!stem || !stem->is_live () || Stem::is_invisible (stem))
222             return;
223
224           stems_drul[d] = stem;
225           beams_drul[d] = Stem::get_beam (stem);
226
227           Direction stem_dir = get_grob_direction (stem);
228           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
229             return;
230
231           stem_dirs[d] = stem_dir;
232
233           /*
234             Correction doesn't seem appropriate  when there is a large flag
235             hanging from the note.
236           */
237           if (d == LEFT
238               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
239             return;
240
241           Interval hp = Stem::head_positions (stem);
242           if (!hp.is_empty ())
243             {
244               Real chord_start = hp[stem_dir];
245
246               /*
247                 can't look at stem-end-position, since that triggers
248                 beam slope computations.
249               */
250               Real stem_end = hp[stem_dir] +
251                 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
252
253               stem_posns[d] = Interval (min (chord_start, stem_end),
254                                         max (chord_start, stem_end));
255               head_posns[d].unite (hp);
256             }
257         }
258     }
259   while (flip (&d) != LEFT);
260
261   Real correction = 0.0;
262
263   if (!bar_yextent.is_empty ())
264     {
265       stem_dirs[RIGHT] = -stem_dirs[LEFT];
266       stem_posns[RIGHT] = bar_yextent;
267       stem_posns[RIGHT] *= 2;
268     }
269
270   if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
271     {
272       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
273         {
274           correction = knee_correction (me, stems_drul[RIGHT], increment);
275           *fixed += correction;
276         }
277       else
278         {
279           correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
280
281           if (!bar_yextent.is_empty ())
282             correction *= 0.5;
283         }
284     }
285   else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1)
286     correction = same_direction_correction (me, head_posns);
287
288   *space += correction;
289
290   /* there used to be a correction for bar_xextent () here, but
291      it's unclear what that was good for ?
292   */
293 }
294
295 ADD_INTERFACE (Note_spacing,
296                "This object calculates spacing wishes for individual voices.",
297
298                
299                "knee-spacing-correction "
300                "left-items "
301                "right-items "
302                "same-direction-correction "
303                "stem-spacing-correction "
304
305                );
306