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