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