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