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