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