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