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