]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
a002d50dc25ed0f8922768ab1beffbea12bffb10
[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--2002  Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9
10 #include "paper-column.hh"
11 #include "item.hh"
12 #include "moment.hh"
13 #include "note-spacing.hh"
14 #include "grob.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
21 bool
22 Note_spacing::has_interface (Grob* g)
23 {
24   return g && g->has_interface (ly_symbol2scm ("note-spacing-interface"));
25 }
26
27 void
28 Note_spacing::get_spacing (Grob *me, Item* right_col,
29                            Real base_space, Real increment, Real *space, Real *fixed)
30 {
31
32   Drul_array<SCM> props(me->get_grob_property ("left-items"),
33                         me->get_grob_property ("right-items"));
34   Direction d = LEFT;
35   Direction col_dir =  right_col->break_status_dir ();
36   Drul_array<Interval> extents;
37   do
38     {
39       for (SCM  s = props[d]; gh_pair_p (s); s = gh_cdr (s))
40         {
41           Item * it= dynamic_cast<Item*> (unsmob_grob (gh_car(s)));
42
43           if (d == RIGHT && it->break_status_dir () != col_dir)
44             {
45               it = it -> find_prebroken_piece (col_dir);
46             }
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           if (d == RIGHT && right_col != it->column_l ())
56             continue;
57           
58           if (Separation_item::has_interface (it))
59             {
60               extents[d].unite (Separation_item::my_width (it));
61               continue;
62             }
63           
64           extents[d].unite (it->extent (it->column_l (), X_AXIS));
65           if (d == RIGHT)
66             {
67               Grob * accs = Note_column::accidentals (it);
68               if (!accs)
69                 accs = Note_column::accidentals (it->get_parent (X_AXIS));
70               
71               if (accs)
72                 extents[d].unite (accs->extent (it->column_l (), X_AXIS));
73             }
74         }
75
76       if (extents[d].empty_b ())
77         extents[d] = Interval (0,0);
78     }
79   while (flip (&d) != LEFT);
80
81   *fixed = (extents[LEFT][RIGHT] >? increment);
82   *space = (base_space - increment) + *fixed ;
83
84   if (*space - *fixed < 2 * ((- extents[RIGHT][LEFT]) >? 0))
85     {
86       /*
87     
88       What's sticking out at the left of the right side has less
89       influence. We only take it into account if there is not enough
90       space.
91
92       this sucks: this criterion is discontinuous; FIXME.
93       */
94       *space += 0.5 * (( -extents[RIGHT][LEFT]) >? 0);
95     }
96
97   *space += stem_dir_correction (me, right_col, increment);
98 }
99
100 Item *
101 Note_spacing::left_column (Grob *me)
102 {
103   if (me->immutable_property_alist_ == SCM_EOL)
104     return 0;
105   
106   return dynamic_cast<Item*> (me)->column_l ();
107 }
108
109 /*
110   Compute the column of the right-items.  This is a big function,
111 since RIGHT-ITEMS may span more columns (eg. if a clef if inserted,
112 this will add a new columns to RIGHT-ITEMS. Here we look at the
113 columns, and return the left-most. If there are multiple columns, we
114 prune RIGHT-ITEMS.
115    
116  */
117 Item *
118 Note_spacing::right_column (Grob*me)
119 {
120   /*
121     ugh. should have generic is_live() method?
122    */
123   if (me->immutable_property_alist_ == SCM_EOL)
124     return 0;
125   
126   SCM right = me->get_grob_property ("right-items");
127   Item *mincol = 0;
128   int min_rank = INT_MAX;
129   bool prune = false;
130   for (SCM s = right ; gh_pair_p (s) ; s = gh_cdr (s))
131     {
132       Item * ri = unsmob_item (gh_car (s));
133
134       Item * col = ri->column_l ();
135       int rank = Paper_column::rank_i (col);
136
137       if (rank < min_rank)
138         {
139           min_rank = rank;
140           if (mincol)
141             prune = true;
142
143           mincol = col;
144         }
145     }
146   
147   if (prune)
148     {
149       // I'm a lazy bum. We could do this in-place.
150       SCM newright  = SCM_EOL;
151       for (SCM s = right ; gh_pair_p (s) ; s =gh_cdr (s))
152         {
153           if (unsmob_item (gh_car (s))->column_l () == mincol)
154             newright = gh_cons (gh_car (s), newright);
155         }
156
157       me->set_grob_property ("right-items", newright);
158     }
159   
160   if (!mincol)
161     {
162       /*
163       int r = Paper_column::rank_i (dynamic_cast<Item*>(me)->column_l ());
164       programming_error (_f("Spacing wish column %d has no right item.", r));
165       */
166
167       return 0;
168     }
169
170   return mincol;
171 }
172
173 /**
174    Correct for optical illusions. See [Wanske] p. 138. The combination
175    up-stem + down-stem should get extra space, the combination
176    down-stem + up-stem less.
177
178    TODO: have to check wether the stems are in the same staff.
179
180 */
181 Real
182 Note_spacing::stem_dir_correction (Grob*me, Item * rcolumn,
183                                    Real increment)  
184 {
185   Drul_array<Direction> stem_dirs(CENTER,CENTER);
186   Drul_array<Interval> stem_posns;
187   Drul_array<Interval> head_posns;  
188   Drul_array<SCM> props(me->get_grob_property ("left-items"),
189                         me->get_grob_property ("right-items"));
190
191   Drul_array<Grob*> beams_drul(0,0);
192   Real correction = 0.0;
193   
194   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
195   Interval intersect;
196   Interval bar_xextent;
197   Interval bar_yextent;  
198   
199   bool correct = true;
200   Direction d = LEFT;
201   bool acc_right = false;
202   
203   do
204     {
205       for (SCM  s = props[d]; gh_pair_p (s); s = gh_cdr (s))
206         {
207           Item * it= dynamic_cast<Item*> (unsmob_grob (gh_car(s)));
208
209           if (d == RIGHT)
210             acc_right = acc_right || Note_column::accidentals (it);
211           
212           Grob *stem = Note_column::stem_l (it);
213
214           if (!stem)
215             {
216               if (d == RIGHT && Separation_item::has_interface (it))
217                 {
218                   if (it->column_l () != rcolumn)
219                     {
220                       it = it->find_prebroken_piece (rcolumn->break_status_dir ());
221                     }
222                   
223                   Grob *last = Staff_spacing::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
224
225                   if (last)
226                     bar_yextent = Staff_spacing::bar_y_positions (last);
227
228                   break;
229                 }
230
231               goto exit_func; 
232             }
233           
234           if(Stem::invisible_b (stem))
235             {
236               correct = false;
237               goto exit_func ;
238             }
239
240           beams_drul[d] = Stem::beam_l (stem);
241             
242           
243           Direction sd = Stem::get_direction (stem);
244           if (stem_dirs[d] && stem_dirs[d] != sd)
245             {
246               correct = false;
247               goto exit_func;
248             }
249           stem_dirs[d] = sd;
250
251           Interval hp  = Stem::head_positions (stem);
252           Real chord_start = hp[sd];      
253           Real stem_end = Stem::stem_end_position (stem);
254           
255           stem_posns[d] = Interval(chord_start<?stem_end, chord_start>? stem_end);
256           head_posns[d].unite (hp);
257         }
258     }
259   while (flip (&d) != LEFT);
260   
261
262   /*
263     don't correct if accidentals are sticking out of the right side.
264
265   */
266   if (acc_right)
267     return 0.0;
268
269   if (!bar_yextent.empty_b())
270     {
271       stem_dirs[RIGHT] = - stem_dirs[LEFT];
272       stem_posns[RIGHT] = bar_yextent;
273     }
274   
275   if (correct &&stem_dirs[LEFT] *stem_dirs[RIGHT] == -1)
276     {
277       if (beams_drul[LEFT] == beams_drul[RIGHT])
278         {
279           /*
280             this is a knee: maximal correction.
281           */
282           
283           correction = increment* stem_dirs[LEFT];
284         }
285       else
286         {
287           intersect = stem_posns[LEFT];  
288           intersect.intersect(stem_posns[RIGHT]);
289           correct = correct && !intersect.empty_b ();
290
291           if (!correct)
292             return 0.0;
293           
294           correction = abs (intersect.length ());         
295
296       
297           /*
298             Ugh. 7 is hardcoded.
299           */
300           correction = (correction/7) <? 1.0;
301           correction *= stem_dirs[LEFT] ;
302           correction *= gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
303
304           if (!bar_yextent.empty_b())
305             {
306               correction *= 0.5;
307             }
308         }
309     }
310   else if (correct && stem_dirs[LEFT] *stem_dirs[RIGHT] == UP)
311     {
312       /*
313         Correct for the following situation:
314
315          X      X
316         |      | 
317         |      |
318         |   X  |
319         |  |   |
320         ========
321
322            ^ move the center one to the left.
323         
324
325         this effect seems to be much more subtle than the
326         stem-direction stuff (why?), and also does not scale with the
327         difference in stem length.
328         
329        */
330
331       
332       Interval hp = head_posns[LEFT];
333       hp.intersect  (head_posns[RIGHT]);
334       if (!hp.empty_b())
335         return 0.0;
336
337       Direction lowest =
338         (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
339
340       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP] ;
341       Real corr = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
342       corr =  (delta <= 1) ? 0.0 : 0.25;
343       
344       correction=  -lowest * corr ;
345     }
346
347   if (!bar_xextent.empty_b())
348     correction += - bar_xextent[LEFT];
349   
350  exit_func:
351   return correction;
352 }
353  
354
355
356
357 ADD_INTERFACE (Note_spacing,"note-spacing-interface",
358   "",
359   "left-items right-items stem-spacing-correction");
360