]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
Merge with master
[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 #if 0
156   /*
157     The below situation is now handled by the "sticks out a lot" case
158     above. However we keep around the code for a few releases before
159     we drop it.
160    */
161   if (!extents[RIGHT].is_empty ()
162       && (Paper_column::is_breakable (right_col)))
163     {
164       /*
165         This is for the situation
166
167         rest | 3/4 (eol)
168
169         Since we only take half of the right-object space above, the
170         barline will bump into the notes preceding it, if the right
171         thing is big. We add the rest of the extents here:
172       */
173
174       *space += -extents[RIGHT][LEFT] / 2;
175       *fixed += -extents[RIGHT][LEFT] / 2;
176     }
177 #endif
178   
179   stem_dir_correction (me, right_col, increment, space, fixed);
180 }
181
182 Item *
183 Note_spacing::left_column (Grob *me)
184 {
185   if (!me->is_live ())
186     return 0;
187
188   return dynamic_cast<Item *> (me)->get_column ();
189 }
190
191 /*
192   Compute the column of the right-items.  This is a big function,
193   since RIGHT-ITEMS may span more columns (eg. if a clef if inserted,
194   this will add a new columns to RIGHT-ITEMS. Here we look at the
195   columns, and return the left-most. If there are multiple columns, we
196   prune RIGHT-ITEMS.
197 */
198 Item *
199 Note_spacing::right_column (Grob *me)
200 {
201   if (!me->is_live ())
202     return 0;
203
204   Grob_array *a = unsmob_grob_array (me->get_object ("right-items"));
205   Item *mincol = 0;
206   int min_rank = INT_MAX;
207   bool prune = false;
208   for (vsize i = 0; a && i < a->size (); i++)
209     {
210       Item *ri = a->item (i);
211       Item *col = ri->get_column ();
212
213       int rank = Paper_column::get_rank (col);
214
215       if (rank < min_rank)
216         {
217           min_rank = rank;
218           if (mincol)
219             prune = true;
220
221           mincol = col;
222         }
223     }
224
225   if (prune && a)
226     {
227       vector<Grob*> &right = a->array_reference ();
228       for (vsize i = right.size (); i--;)
229         {
230           if (dynamic_cast<Item *> (right[i])->get_column () != mincol)
231             right.erase (right.begin () + i);
232         }
233     }
234
235   if (!mincol)
236     return 0;
237
238   return mincol;
239 }
240
241 /**
242    Correct for optical illusions. See [Wanske] p. 138. The combination
243    up-stem + down-stem should get extra space, the combination
244    down-stem + up-stem less.
245
246    TODO: have to check wether the stems are in the same staff.
247 */
248 void
249 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
250                                    Real increment,
251                                    Real *space, Real *fixed)
252 {
253   Drul_array<Direction> stem_dirs (CENTER, CENTER);
254   Drul_array<Interval> stem_posns;
255   Drul_array<Interval> head_posns;
256   Drul_array<SCM> props (me->get_object ("left-items"),
257                          me->get_object ("right-items"));
258
259   Drul_array<Spanner *> beams_drul (0, 0);
260   Drul_array<Grob *> stems_drul (0, 0);
261
262   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
263   Interval intersect;
264   Interval bar_xextent;
265   Interval bar_yextent;
266
267   bool correct_stem_dirs = true;
268   Direction d = LEFT;
269   bool acc_right = false;
270
271   do
272     {
273       vector<Grob*> const &items (ly_scm2link_array (props [d]));
274       for (vsize i = 0; i < items.size (); i++)
275         {
276           Item *it = dynamic_cast<Item *> (items[i]);
277
278           if (d == RIGHT)
279             acc_right = acc_right || Note_column::accidentals (it);
280
281           Grob *stem = Note_column::get_stem (it);
282
283           if (!stem || !stem->is_live ())
284             {
285               if (d == RIGHT && Separation_item::has_interface (it))
286                 {
287                   if (it->get_column () != rcolumn)
288                     it = it->find_prebroken_piece (rcolumn->break_status_dir ());
289
290                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
291
292                   if (last)
293                     bar_yextent = Staff_spacing::bar_y_positions (last);
294
295                   break;
296                 }
297
298               return;
299             }
300
301           if (Stem::is_invisible (stem))
302             {
303               correct_stem_dirs = false;
304               continue;
305             }
306
307           stems_drul[d] = stem;
308           beams_drul[d] = Stem::get_beam (stem);
309
310           Direction stem_dir = get_grob_direction (stem);
311           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
312             {
313               correct_stem_dirs = false;
314               continue;
315             }
316           stem_dirs[d] = stem_dir;
317
318           /*
319             Correction doesn't seem appropriate  when there is a large flag
320             hanging from the note.
321           */
322           if (d == LEFT
323               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
324             correct_stem_dirs = false;
325
326           Interval hp = Stem::head_positions (stem);
327           if (correct_stem_dirs
328               && !hp.is_empty ())
329             {
330               Real chord_start = hp[stem_dir];
331
332               /*
333                 can't look at stem-end-position, since that triggers
334                 beam slope computations.
335               */
336               Real stem_end = hp[stem_dir] +
337                 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
338
339               stem_posns[d] = Interval (min (chord_start, stem_end),
340                                         max (chord_start, stem_end));
341               head_posns[d].unite (hp);
342             }
343         }
344     }
345   while (flip (&d) != LEFT);
346
347   /*
348     don't correct if accidentals are sticking out of the right side.
349   */
350   if (acc_right)
351     return;
352
353   Real correction = 0.0;
354
355   if (!bar_yextent.is_empty ())
356     {
357       stem_dirs[RIGHT] = -stem_dirs[LEFT];
358       stem_posns[RIGHT] = bar_yextent;
359       stem_posns[RIGHT] *= 2;
360     }
361
362   if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
363     {
364       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
365         {
366
367           /*
368             this is a knee: maximal correction.
369           */
370           Real note_head_width = increment;
371           Grob *st = stems_drul[RIGHT];
372           Grob *head = st ? Stem::support_head (st) : 0;
373
374           Interval head_extent;
375           if (head)
376             {
377               head_extent = head->extent (rcolumn, X_AXIS);
378
379               if (!head_extent.is_empty ())
380                 note_head_width = head_extent[RIGHT];
381
382               if (st)
383                 {
384                   Real thick = Stem::thickness (st);
385
386                   note_head_width -= thick;
387                 }
388             }
389
390           correction = note_head_width * stem_dirs[LEFT];
391           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
392           *fixed += correction;
393         }
394       else
395         {
396           intersect = stem_posns[LEFT];
397           intersect.intersect (stem_posns[RIGHT]);
398           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
399
400           if (correct_stem_dirs)
401             {
402               correction = abs (intersect.length ());
403
404               /*
405                 Ugh. 7 is hardcoded.
406               */
407               correction = min (correction / 7, 1.0);
408               correction *= stem_dirs[LEFT];
409               correction
410                 *= robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
411             }
412
413           if (!bar_yextent.is_empty ())
414             correction *= 0.5;
415         }
416     }
417   else if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == UP)
418     {
419       /*
420         Correct for the following situation:
421
422         X      X
423         |      |
424         |      |
425         |   X  |
426         |  |   |
427         ========
428
429         ^ move the center one to the left.
430
431
432         this effect seems to be much more subtle than the
433         stem-direction stuff (why?), and also does not scale with the
434         difference in stem length.
435
436       */
437
438       Interval hp = head_posns[LEFT];
439       hp.intersect (head_posns[RIGHT]);
440       if (!hp.is_empty ())
441         return;
442
443       Direction lowest
444         = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
445
446       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
447       Real corr = robust_scm2double (me->get_property ("same-direction-correction"), 0);
448
449       if (delta > 1)
450         correction = -lowest * corr;
451     }
452
453   *space += correction;
454
455   /* there used to be a correction for bar_xextent () here, but
456      it's unclear what that was good for ?
457   */
458 }
459
460 ADD_INTERFACE (Note_spacing,
461                "This object calculates spacing wishes for individual voices.",
462
463                
464                "knee-spacing-correction "
465                "left-items "
466                "right-items "
467                "same-direction-correction "
468                "stem-spacing-correction "
469
470                );
471