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