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