]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
* The grand 2005-2006 replace.
[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--2006  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       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             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             {
69               SCM r = it->get_object ("rest");
70               Grob *g = unsmob_grob (r);
71               if (!g)
72                 g = Note_column::first_head (it);
73
74               /*
75                 Ugh. If Stem is switched off, we don't know what the
76                 first note head will be.
77               */
78               if (g)
79                 left_head_wid = g->extent (it_col, X_AXIS);
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     eg. a quarter rest gets almost 0.5 ss less horizontal space than a note.
110
111     What is sticking out of the note head (eg. a flag), doesn't get
112     the full amount of space.
113
114     FIXED also includes the left part of the right object.
115   */
116   *fixed
117     = (left_head_wid.is_empty () ? increment
118        :        /*
119                   Size of the head:
120                 */
121        (left_head_wid[RIGHT]+
122
123         /*
124           What's sticking out of the head, eg. a flag:
125         */
126         (extents[LEFT][RIGHT] - left_head_wid[RIGHT]) / 2))
127
128     /*
129       What is sticking out on the left side of the right note:
130     */
131     + (extents[RIGHT].is_empty ()
132        ? 0.0
133        : ((- extents[RIGHT][LEFT] / 2)
134
135           /*
136             Add that which sticks out a lot.
137           */
138           + max (0.0, -extents[RIGHT][LEFT] - (base_space - increment))));
139
140   /*
141     We don't do complicated stuff: (base_space - increment) is the
142     normal amount of white, which also determines the amount of
143     stretch. Upon (extreme) stretching, notes with accidentals should
144     stretch as much as notes without accidentals.
145   */
146   *space = (base_space - increment) + *fixed;
147
148 #if 0
149   /*
150     The below situation is now handled by the "sticks out a lot" case
151     above. However we keep around the code for a few releases before
152     we drop it.
153    */
154   if (!extents[RIGHT].is_empty ()
155       && (Item::is_breakable (right_col)
156           || right_col->original ()))
157     {
158       /*
159         This is for the situation
160
161         rest | 3/4 (eol)
162
163         Since we only take half of the right-object space above, the
164         barline will bump into the notes preceding it, if the right
165         thing is big. We add the rest of the extents here:
166       */
167
168       *space += -extents[RIGHT][LEFT] / 2;
169       *fixed += -extents[RIGHT][LEFT] / 2;
170     }
171 #endif
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     return 0;
231
232   return mincol;
233 }
234
235 /**
236    Correct for optical illusions. See [Wanske] p. 138. The combination
237    up-stem + down-stem should get extra space, the combination
238    down-stem + up-stem less.
239
240    TODO: have to check wether the stems are in the same staff.
241 */
242 void
243 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
244                                    Real increment,
245                                    Real *space, Real *fixed)
246 {
247   Drul_array<Direction> stem_dirs (CENTER, CENTER);
248   Drul_array<Interval> stem_posns;
249   Drul_array<Interval> head_posns;
250   Drul_array<SCM> props (me->get_object ("left-items"),
251                          me->get_object ("right-items"));
252
253   Drul_array<Spanner *> beams_drul (0, 0);
254   Drul_array<Grob *> stems_drul (0, 0);
255
256   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
257   Interval intersect;
258   Interval bar_xextent;
259   Interval bar_yextent;
260
261   bool correct_stem_dirs = true;
262   Direction d = LEFT;
263   bool acc_right = false;
264
265   do
266     {
267       Link_array<Grob> const &items (ly_scm2link_array (props [d]));
268       for (int i = 0; i < items.size (); i++)
269         {
270           Item *it = dynamic_cast<Item *> (items[i]);
271
272           if (d == RIGHT)
273             acc_right = acc_right || Note_column::accidentals (it);
274
275           Grob *stem = Note_column::get_stem (it);
276
277           if (!stem || !stem->is_live ())
278             {
279               if (d == RIGHT && Separation_item::has_interface (it))
280                 {
281                   if (it->get_column () != rcolumn)
282                     it = it->find_prebroken_piece (rcolumn->break_status_dir ());
283
284                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
285
286                   if (last)
287                     bar_yextent = Staff_spacing::bar_y_positions (last);
288
289                   break;
290                 }
291
292               return;
293             }
294
295           if (Stem::is_invisible (stem))
296             {
297               correct_stem_dirs = false;
298               continue;
299             }
300
301           stems_drul[d] = stem;
302           beams_drul[d] = Stem::get_beam (stem);
303
304           Direction sd = get_grob_direction (stem);
305           if (stem_dirs[d] && stem_dirs[d] != sd)
306             {
307               correct_stem_dirs = false;
308               continue;
309             }
310           stem_dirs[d] = sd;
311
312           /*
313             Correction doesn't seem appropriate  when there is a large flag
314             hanging from the note.
315           */
316           if (d == LEFT
317               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
318             correct_stem_dirs = false;
319
320           Interval hp = Stem::head_positions (stem);
321           if (!hp.is_empty ())
322             {
323               Real chord_start = hp[sd];
324               Real stem_end = Stem::stem_end_position (stem);
325
326               stem_posns[d] = Interval (min (chord_start, stem_end), max (chord_start, stem_end));
327               head_posns[d].unite (hp);
328             }
329         }
330     }
331   while (flip (&d) != LEFT);
332
333   /*
334     don't correct if accidentals are sticking out of the right side.
335   */
336   if (acc_right)
337     return;
338
339   Real correction = 0.0;
340
341   if (!bar_yextent.is_empty ())
342     {
343       stem_dirs[RIGHT] = -stem_dirs[LEFT];
344       stem_posns[RIGHT] = bar_yextent;
345     }
346
347   if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
348     {
349       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
350         {
351
352           /*
353             this is a knee: maximal correction.
354           */
355           Real note_head_width = increment;
356           Grob *st = stems_drul[RIGHT];
357           Grob *head = st ? Stem::support_head (st) : 0;
358
359           Interval head_extent;
360           if (head)
361             {
362               head_extent = head->extent (rcolumn, X_AXIS);
363
364               if (!head_extent.is_empty ())
365                 note_head_width = head_extent[RIGHT];
366
367               if (st)
368                 {
369                   Real thick = Stem::thickness (st);
370
371                   note_head_width -= thick;
372                 }
373             }
374
375           correction = note_head_width * stem_dirs[LEFT];
376           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
377           *fixed += correction;
378         }
379       else
380         {
381           intersect = stem_posns[LEFT];
382           intersect.intersect (stem_posns[RIGHT]);
383           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
384
385           if (correct_stem_dirs)
386             {
387               correction = abs (intersect.length ());
388
389               /*
390                 Ugh. 7 is hardcoded.
391               */
392               correction = min (correction / 7, 1.0);
393               correction *= stem_dirs[LEFT];
394               correction
395                 *= robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
396             }
397
398           if (!bar_yextent.is_empty ())
399             correction *= 0.5;
400         }
401     }
402   else if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == UP)
403     {
404       /*
405         Correct for the following situation:
406
407         X      X
408         |      |
409         |      |
410         |   X  |
411         |  |   |
412         ========
413
414         ^ move the center one to the left.
415
416
417         this effect seems to be much more subtle than the
418         stem-direction stuff (why?), and also does not scale with the
419         difference in stem length.
420
421       */
422
423       Interval hp = head_posns[LEFT];
424       hp.intersect (head_posns[RIGHT]);
425       if (!hp.is_empty ())
426         return;
427
428       Direction lowest
429         = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
430
431       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
432       Real corr = robust_scm2double (me->get_property ("same-direction-correction"), 0);
433
434       if (delta > 1)
435         correction = -lowest * corr;
436     }
437
438   *space += correction;
439
440   /* there used to be a correction for bar_xextent () here, but
441      it's unclear what that was good for ?
442   */
443 }
444
445 ADD_INTERFACE (Note_spacing, "note-spacing-interface",
446                "This object calculates spacing wishes for individual voices.",
447                "left-items right-items stem-spacing-correction same-direction-correction knee-spacing-correction");
448