]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
introduce Spacing_interface for code sharing between Note_spacing and Staff_spacing
[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 (d == LEFT
62               && Note_column::has_interface (it))
63             {
64               SCM r = it->get_object ("rest");
65               Grob *g = unsmob_grob (r);
66               if (!g)
67                 g = Note_column::first_head (it);
68
69               /*
70                 Ugh. If Stem is switched off, we don't know what the
71                 first note head will be.
72               */
73               if (g)
74                 {
75                   if (g->common_refpoint (it_col, X_AXIS) != it_col)
76                     programming_error ("Note_spacing::get_spacing (): Common refpoint incorrect");
77                   else
78                     left_head_wid = g->extent (it_col, X_AXIS);
79                 }
80             }
81
82           extents[d].unite (it->extent (it_col, X_AXIS));
83           if (d == RIGHT)
84             {
85               Grob *accs = Note_column::accidentals (it);
86               if (!accs)
87                 accs = Note_column::accidentals (it->get_parent (X_AXIS));
88
89               if (accs)
90                 {
91                   Interval v
92                     = Accidental_placement::get_relevant_accidental_extent (accs, it_col, me);
93
94                   extents[d].unite (v);
95                 }
96
97               if (Grob *arpeggio = Note_column::arpeggio (it))
98                 extents[d].unite (arpeggio->extent (it_col, X_AXIS));
99             }
100         }
101
102       if (extents[d].is_empty ())
103         extents[d] = Interval (0, 0);
104     }
105   while (flip (&d) != LEFT);
106
107   /*
108     We look at the width of the note head, since smaller heads get less space
109
110     eg. a quarter rest gets almost 0.5 ss less horizontal space than a note.
111
112     What is sticking out of the note head (eg. a flag), doesn't get
113     the full amount of space.
114
115     FIXED also includes the left part of the right object.
116   */
117   *fixed
118     = (left_head_wid.is_empty () ? increment
119        :        /*
120                   Size of the head:
121                 */
122        (left_head_wid[RIGHT]+
123
124         /*
125           What's sticking out of the head, eg. a flag:
126         */
127         (extents[LEFT][RIGHT] - left_head_wid[RIGHT]) / 2))
128
129     /*
130       What is sticking out on the left side of the right note:
131     */
132     + (extents[RIGHT].is_empty ()
133        ? 0.0
134        : ((- extents[RIGHT][LEFT] / 2)
135
136           /*
137             Add that which sticks out a lot.
138           */
139           + max (0.0, -extents[RIGHT][LEFT] - (base_space - 0.5 * increment))));
140
141   /*
142     We don't do complicated stuff: (base_space - increment) is the
143     normal amount of white, which also determines the amount of
144     stretch. Upon (extreme) stretching, notes with accidentals should
145     stretch as much as notes without accidentals.
146   */
147   *space = (base_space - increment) + *fixed;
148
149   stem_dir_correction (me, right_col, increment, space, fixed);
150 }
151
152
153 /**
154    Correct for optical illusions. See [Wanske] p. 138. The combination
155    up-stem + down-stem should get extra space, the combination
156    down-stem + up-stem less.
157
158    TODO: have to check wether the stems are in the same staff.
159 */
160 void
161 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
162                                    Real increment,
163                                    Real *space, Real *fixed)
164 {
165   Drul_array<Direction> stem_dirs (CENTER, CENTER);
166   Drul_array<Interval> stem_posns;
167   Drul_array<Interval> head_posns;
168   Drul_array<SCM> props (me->get_object ("left-items"),
169                          me->get_object ("right-items"));
170
171   Drul_array<Spanner *> beams_drul (0, 0);
172   Drul_array<Grob *> stems_drul (0, 0);
173
174   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
175   Interval intersect;
176   Interval bar_xextent;
177   Interval bar_yextent;
178
179   bool correct_stem_dirs = true;
180   Direction d = LEFT;
181   bool acc_right = false;
182
183   do
184     {
185       vector<Grob*> const &items (ly_scm2link_array (props [d]));
186       for (vsize i = 0; i < items.size (); i++)
187         {
188           Item *it = dynamic_cast<Item *> (items[i]);
189
190           if (d == RIGHT)
191             acc_right = acc_right || Note_column::accidentals (it);
192
193           Grob *stem = Note_column::get_stem (it);
194
195           if (!stem || !stem->is_live ())
196             {
197               if (d == RIGHT && Separation_item::has_interface (it))
198                 {
199                   if (it->get_column () != rcolumn)
200                     it = it->find_prebroken_piece (rcolumn->break_status_dir ());
201
202                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
203
204                   if (last)
205                     bar_yextent = Staff_spacing::bar_y_positions (last);
206
207                   break;
208                 }
209
210               return;
211             }
212
213           if (Stem::is_invisible (stem))
214             {
215               correct_stem_dirs = false;
216               continue;
217             }
218
219           stems_drul[d] = stem;
220           beams_drul[d] = Stem::get_beam (stem);
221
222           Direction stem_dir = get_grob_direction (stem);
223           if (stem_dirs[d] && stem_dirs[d] != stem_dir)
224             {
225               correct_stem_dirs = false;
226               continue;
227             }
228           stem_dirs[d] = stem_dir;
229
230           /*
231             Correction doesn't seem appropriate  when there is a large flag
232             hanging from the note.
233           */
234           if (d == LEFT
235               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
236             correct_stem_dirs = false;
237
238           Interval hp = Stem::head_positions (stem);
239           if (correct_stem_dirs
240               && !hp.is_empty ())
241             {
242               Real chord_start = hp[stem_dir];
243
244               /*
245                 can't look at stem-end-position, since that triggers
246                 beam slope computations.
247               */
248               Real stem_end = hp[stem_dir] +
249                 stem_dir * robust_scm2double (stem->get_property ("length"), 7);
250
251               stem_posns[d] = Interval (min (chord_start, stem_end),
252                                         max (chord_start, stem_end));
253               head_posns[d].unite (hp);
254             }
255         }
256     }
257   while (flip (&d) != LEFT);
258
259   /*
260     don't correct if accidentals are sticking out of the right side.
261   */
262   if (acc_right)
263     return;
264
265   Real correction = 0.0;
266
267   if (!bar_yextent.is_empty ())
268     {
269       stem_dirs[RIGHT] = -stem_dirs[LEFT];
270       stem_posns[RIGHT] = bar_yextent;
271       stem_posns[RIGHT] *= 2;
272     }
273
274   if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
275     {
276       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
277         {
278
279           /*
280             this is a knee: maximal correction.
281           */
282           Real note_head_width = increment;
283           Grob *st = stems_drul[RIGHT];
284           Grob *head = st ? Stem::support_head (st) : 0;
285
286           Interval head_extent;
287           if (head)
288             {
289               head_extent = head->extent (rcolumn, X_AXIS);
290
291               if (!head_extent.is_empty ())
292                 note_head_width = head_extent[RIGHT];
293
294               if (st)
295                 {
296                   Real thick = Stem::thickness (st);
297
298                   note_head_width -= thick;
299                 }
300             }
301
302           correction = note_head_width * stem_dirs[LEFT];
303           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
304           *fixed += correction;
305         }
306       else
307         {
308           intersect = stem_posns[LEFT];
309           intersect.intersect (stem_posns[RIGHT]);
310           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
311
312           if (correct_stem_dirs)
313             {
314               correction = abs (intersect.length ());
315
316               /*
317                 Ugh. 7 is hardcoded.
318               */
319               correction = min (correction / 7, 1.0);
320               correction *= stem_dirs[LEFT];
321               correction
322                 *= robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
323             }
324
325           if (!bar_yextent.is_empty ())
326             correction *= 0.5;
327         }
328     }
329   else if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == UP)
330     {
331       /*
332         Correct for the following situation:
333
334         X      X
335         |      |
336         |      |
337         |   X  |
338         |  |   |
339         ========
340
341         ^ move the center one to the left.
342
343
344         this effect seems to be much more subtle than the
345         stem-direction stuff (why?), and also does not scale with the
346         difference in stem length.
347
348       */
349
350       Interval hp = head_posns[LEFT];
351       hp.intersect (head_posns[RIGHT]);
352       if (!hp.is_empty ())
353         return;
354
355       Direction lowest
356         = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
357
358       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
359       Real corr = robust_scm2double (me->get_property ("same-direction-correction"), 0);
360
361       if (delta > 1)
362         correction = -lowest * corr;
363     }
364
365   *space += correction;
366
367   /* there used to be a correction for bar_xextent () here, but
368      it's unclear what that was good for ?
369   */
370 }
371
372 ADD_INTERFACE (Note_spacing,
373                "This object calculates spacing wishes for individual voices.",
374
375                
376                "knee-spacing-correction "
377                "left-items "
378                "right-items "
379                "same-direction-correction "
380                "stem-spacing-correction "
381
382                );
383