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