]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
Tweak spacing between a musical and a non-musical column.
[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) / 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 - max (min_dist, 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
219           /*
220             don't correct if accidentals are sticking out of the right side.
221           */
222           if (d == RIGHT && Note_column::accidentals (it))
223             return;
224
225           Grob *stem = Note_column::get_stem (it);
226
227           if (!stem || !stem->is_live () || Stem::is_invisible (stem))
228             return;
229
230           stems_drul[d] = stem;
231           beams_drul[d] = Stem::get_beam (stem);
232
233           Direction stem_dir = get_grob_direction (stem);
234           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
235             return;
236
237           stem_dirs[d] = stem_dir;
238
239           /*
240             Correction doesn't seem appropriate  when there is a large flag
241             hanging from the note.
242           */
243           if (d == LEFT
244               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
245             return;
246
247           Interval hp = Stem::head_positions (stem);
248           if (!hp.is_empty ())
249             {
250               Real chord_start = hp[stem_dir];
251
252               /*
253                 can't look at stem-end-position, since that triggers
254                 beam slope computations.
255               */
256               Real stem_end = hp[stem_dir] +
257                 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
258
259               stem_posns[d] = Interval (min (chord_start, stem_end),
260                                         max (chord_start, stem_end));
261               head_posns[d].unite (hp);
262             }
263         }
264     }
265   while (flip (&d) != LEFT);
266
267   Real correction = 0.0;
268
269   if (!bar_yextent.is_empty ())
270     {
271       stem_dirs[RIGHT] = -stem_dirs[LEFT];
272       stem_posns[RIGHT] = bar_yextent;
273       stem_posns[RIGHT] *= 2;
274     }
275
276   if (stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
277     {
278       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
279         {
280           correction = knee_correction (me, stems_drul[RIGHT], increment);
281           *fixed += correction;
282         }
283       else
284         {
285           correction = different_directions_correction (me, stem_posns, stem_dirs[LEFT]);
286
287           if (!bar_yextent.is_empty ())
288             correction *= 0.5;
289         }
290     }
291   else if (stem_dirs[LEFT] * stem_dirs[RIGHT] == 1)
292     correction = same_direction_correction (me, head_posns);
293
294   *space += correction;
295
296   /* there used to be a correction for bar_xextent () here, but
297      it's unclear what that was good for ?
298   */
299 }
300
301 ADD_INTERFACE (Note_spacing,
302                "This object calculates spacing wishes for individual voices.",
303
304                
305                "knee-spacing-correction "
306                "left-items "
307                "right-items "
308                "same-direction-correction "
309                "stem-spacing-correction "
310
311                );
312