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