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