]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
cleanups in Note_spacing
[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 "directional-element-interface.hh"
12 #include "grob-array.hh"
13 #include "paper-column.hh"
14 #include "moment.hh"
15 #include "note-column.hh"
16 #include "warn.hh"
17 #include "stem.hh"
18 #include "separation-item.hh"
19 #include "staff-spacing.hh"
20 #include "accidental-placement.hh"
21 #include "output-def.hh"
22 #include "pointer-group-interface.hh"
23
24 /*
25   TODO: detect hshifts due to collisions, and account for them in
26   spacing?
27 */
28
29 void
30 Note_spacing::get_spacing (Grob *me, Item *right_col,
31                            Real base_space, Real increment, Real *space, Real *fixed)
32 {
33   Drul_array<SCM> props (me->get_object ("left-items"),
34                          me->get_object ("right-items"));
35   Direction d = LEFT;
36   Direction col_dir = right_col->break_status_dir ();
37   Drul_array<Interval> extents;
38
39   Interval left_head_wid;
40   do
41     {
42       vector<Grob*> const &items (ly_scm2link_array (props [d]));
43       for (vsize i = items.size (); i--;)
44         {
45           Item *it = dynamic_cast<Item *> (items[i]);
46
47           if (d == RIGHT && it->break_status_dir () != col_dir)
48             it = it->find_prebroken_piece (col_dir);
49
50           /*
51             some kind of mismatch, eg. a note column, that is behind a
52             linebreak.
53           */
54           if (!it)
55             continue;
56
57           Item *it_col = it->get_column ();
58           if (d == RIGHT && right_col != it_col)
59             continue;
60
61           if (Separation_item::has_interface (it))
62             {
63               extents[d].unite (Separation_item::width (it));
64               continue;
65             }
66
67           if (d == LEFT
68               && Note_column::has_interface (it))
69             {
70               SCM r = it->get_object ("rest");
71               Grob *g = unsmob_grob (r);
72               if (!g)
73                 g = Note_column::first_head (it);
74
75               /*
76                 Ugh. If Stem is switched off, we don't know what the
77                 first note head will be.
78               */
79               if (g)
80                 {
81                   if (g->common_refpoint (it_col, X_AXIS) != it_col)
82                     programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
83                   else
84                     left_head_wid = g->extent (it_col, X_AXIS);
85                 }
86             }
87
88           extents[d].unite (it->extent (it_col, X_AXIS));
89           if (d == RIGHT)
90             {
91               Grob *accs = Note_column::accidentals (it);
92               if (!accs)
93                 accs = Note_column::accidentals (it->get_parent (X_AXIS));
94
95               if (accs)
96                 {
97                   Interval v
98                     = Accidental_placement::get_relevant_accidental_extent (accs, it_col, me);
99
100                   extents[d].unite (v);
101                 }
102
103               if (Grob *arpeggio = Note_column::arpeggio (it))
104                 extents[d].unite (arpeggio->extent (it_col, X_AXIS));
105             }
106         }
107
108       if (extents[d].is_empty ())
109         extents[d] = Interval (0, 0);
110     }
111   while (flip (&d) != LEFT);
112
113   /*
114     We look at the width of the note head, since smaller heads get less space
115
116     eg. a quarter rest gets almost 0.5 ss less horizontal space than a note.
117
118     What is sticking out of the note head (eg. a flag), doesn't get
119     the full amount of space.
120
121     FIXED also includes the left part of the right object.
122   */
123   *fixed
124     = (left_head_wid.is_empty () ? increment
125        :        /*
126                   Size of the head:
127                 */
128        (left_head_wid[RIGHT]+
129
130         /*
131           What's sticking out of the head, eg. a flag:
132         */
133         (extents[LEFT][RIGHT] - left_head_wid[RIGHT]) / 2))
134
135     /*
136       What is sticking out on the left side of the right note:
137     */
138     + (extents[RIGHT].is_empty ()
139        ? 0.0
140        : ((- extents[RIGHT][LEFT] / 2)
141
142           /*
143             Add that which sticks out a lot.
144           */
145           + max (0.0, -extents[RIGHT][LEFT] - (base_space - 0.5 * increment))));
146
147   /*
148     We don't do complicated stuff: (base_space - increment) is the
149     normal amount of white, which also determines the amount of
150     stretch. Upon (extreme) stretching, notes with accidentals should
151     stretch as much as notes without accidentals.
152   */
153   *space = (base_space - increment) + *fixed;
154
155   stem_dir_correction (me, right_col, increment, space, fixed);
156 }
157
158 Item *
159 Note_spacing::left_column (Grob *me)
160 {
161   if (!me->is_live ())
162     return 0;
163
164   return dynamic_cast<Item *> (me)->get_column ();
165 }
166
167 /*
168   Compute the column of the right-items.  This is a big function,
169   since RIGHT-ITEMS may span more columns (eg. if a clef is inserted,
170   this will add a new column to RIGHT-ITEMS. Here we look at the
171   columns, and return the left-most. If there are multiple columns, we
172   prune RIGHT-ITEMS.
173 */
174 Item *
175 Note_spacing::right_column (Grob *me)
176 {
177   if (!me->is_live ())
178     return 0;
179
180   Grob_array *a = unsmob_grob_array (me->get_object ("right-items"));
181   Item *mincol = 0;
182   int min_rank = INT_MAX;
183   bool prune = false;
184   for (vsize i = 0; a && i < a->size (); i++)
185     {
186       Item *ri = a->item (i);
187       Item *col = ri->get_column ();
188
189       int rank = Paper_column::get_rank (col);
190
191       if (rank < min_rank)
192         {
193           min_rank = rank;
194           if (mincol)
195             prune = true;
196
197           mincol = col;
198         }
199     }
200
201   if (prune && a)
202     {
203       vector<Grob*> &right = a->array_reference ();
204       for (vsize i = right.size (); i--;)
205         {
206           if (dynamic_cast<Item *> (right[i])->get_column () != mincol)
207             right.erase (right.begin () + i);
208         }
209     }
210
211   return mincol;
212 }
213
214 /**
215    Correct for optical illusions. See [Wanske] p. 138. The combination
216    up-stem + down-stem should get extra space, the combination
217    down-stem + up-stem less.
218
219    TODO: have to check wether the stems are in the same staff.
220 */
221 void
222 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
223                                    Real increment,
224                                    Real *space, Real *fixed)
225 {
226   Drul_array<Direction> stem_dirs (CENTER, CENTER);
227   Drul_array<Interval> stem_posns;
228   Drul_array<Interval> head_posns;
229   Drul_array<SCM> props (me->get_object ("left-items"),
230                          me->get_object ("right-items"));
231
232   Drul_array<Spanner *> beams_drul (0, 0);
233   Drul_array<Grob *> stems_drul (0, 0);
234
235   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
236   Interval intersect;
237   Interval bar_xextent;
238   Interval bar_yextent;
239
240   bool correct_stem_dirs = true;
241   Direction d = LEFT;
242   bool acc_right = false;
243
244   do
245     {
246       vector<Grob*> const &items (ly_scm2link_array (props [d]));
247       for (vsize i = 0; i < items.size (); i++)
248         {
249           Item *it = dynamic_cast<Item *> (items[i]);
250
251           if (d == RIGHT)
252             acc_right = acc_right || Note_column::accidentals (it);
253
254           Grob *stem = Note_column::get_stem (it);
255
256           if (!stem || !stem->is_live ())
257             {
258               if (d == RIGHT && Separation_item::has_interface (it))
259                 {
260                   if (it->get_column () != rcolumn)
261                     it = it->find_prebroken_piece (rcolumn->break_status_dir ());
262
263                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
264
265                   if (last)
266                     bar_yextent = Staff_spacing::bar_y_positions (last);
267
268                   break;
269                 }
270
271               return;
272             }
273
274           if (Stem::is_invisible (stem))
275             {
276               correct_stem_dirs = false;
277               continue;
278             }
279
280           stems_drul[d] = stem;
281           beams_drul[d] = Stem::get_beam (stem);
282
283           Direction stem_dir = get_grob_direction (stem);
284           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
285             {
286               correct_stem_dirs = false;
287               continue;
288             }
289           stem_dirs[d] = stem_dir;
290
291           /*
292             Correction doesn't seem appropriate  when there is a large flag
293             hanging from the note.
294           */
295           if (d == LEFT
296               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
297             correct_stem_dirs = false;
298
299           Interval hp = Stem::head_positions (stem);
300           if (correct_stem_dirs
301               && !hp.is_empty ())
302             {
303               Real chord_start = hp[stem_dir];
304
305               /*
306                 can't look at stem-end-position, since that triggers
307                 beam slope computations.
308               */
309               Real stem_end = hp[stem_dir] +
310                 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
311
312               stem_posns[d] = Interval (min (chord_start, stem_end),
313                                         max (chord_start, stem_end));
314               head_posns[d].unite (hp);
315             }
316         }
317     }
318   while (flip (&d) != LEFT);
319
320   /*
321     don't correct if accidentals are sticking out of the right side.
322   */
323   if (acc_right)
324     return;
325
326   Real correction = 0.0;
327
328   if (!bar_yextent.is_empty ())
329     {
330       stem_dirs[RIGHT] = -stem_dirs[LEFT];
331       stem_posns[RIGHT] = bar_yextent;
332       stem_posns[RIGHT] *= 2;
333     }
334
335   if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
336     {
337       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
338         {
339
340           /*
341             this is a knee: maximal correction.
342           */
343           Real note_head_width = increment;
344           Grob *st = stems_drul[RIGHT];
345           Grob *head = st ? Stem::support_head (st) : 0;
346
347           Interval head_extent;
348           if (head)
349             {
350               head_extent = head->extent (rcolumn, X_AXIS);
351
352               if (!head_extent.is_empty ())
353                 note_head_width = head_extent[RIGHT];
354
355               if (st)
356                 {
357                   Real thick = Stem::thickness (st);
358
359                   note_head_width -= thick;
360                 }
361             }
362
363           correction = note_head_width * stem_dirs[LEFT];
364           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
365           *fixed += correction;
366         }
367       else
368         {
369           intersect = stem_posns[LEFT];
370           intersect.intersect (stem_posns[RIGHT]);
371           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
372
373           if (correct_stem_dirs)
374             {
375               correction = abs (intersect.length ());
376
377               /*
378                 Ugh. 7 is hardcoded.
379               */
380               correction = min (correction / 7, 1.0);
381               correction *= stem_dirs[LEFT];
382               correction
383                 *= robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
384             }
385
386           if (!bar_yextent.is_empty ())
387             correction *= 0.5;
388         }
389     }
390   else if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == UP)
391     {
392       /*
393         Correct for the following situation:
394
395         X      X
396         |      |
397         |      |
398         |   X  |
399         |  |   |
400         ========
401
402         ^ move the center one to the left.
403
404
405         this effect seems to be much more subtle than the
406         stem-direction stuff (why?), and also does not scale with the
407         difference in stem length.
408
409       */
410
411       Interval hp = head_posns[LEFT];
412       hp.intersect (head_posns[RIGHT]);
413       if (!hp.is_empty ())
414         return;
415
416       Direction lowest
417         = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
418
419       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
420       Real corr = robust_scm2double (me->get_property ("same-direction-correction"), 0);
421
422       if (delta > 1)
423         correction = -lowest * corr;
424     }
425
426   *space += correction;
427
428   /* there used to be a correction for bar_xextent () here, but
429      it's unclear what that was good for ?
430   */
431 }
432
433 ADD_INTERFACE (Note_spacing,
434                "This object calculates spacing wishes for individual voices.",
435
436                
437                "knee-spacing-correction "
438                "left-items "
439                "right-items "
440                "same-direction-correction "
441                "stem-spacing-correction "
442
443                );
444