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