]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
afd1a0962f91bff6545127dd452c698607befb65
[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   stem_dir_correction (me, right_col, increment, space, fixed);
117 }
118
119 Item *
120 Note_spacing::left_column (Grob *me)
121 {
122   if (!me->live())
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   if (!me->live())
140     return 0;
141   
142   SCM right = me->get_grob_property ("right-items");
143   Item *mincol = 0;
144   int min_rank = INT_MAX;
145   bool prune = false;
146   for (SCM s = right ; gh_pair_p (s) ; s = gh_cdr (s))
147     {
148       Item * ri = unsmob_item (gh_car (s));
149
150       Item * col = ri->column_l ();
151       int rank = Paper_column::rank_i (col);
152
153       if (rank < min_rank)
154         {
155           min_rank = rank;
156           if (mincol)
157             prune = true;
158
159           mincol = col;
160         }
161     }
162   
163   if (prune)
164     {
165       // I'm a lazy bum. We could do this in-place.
166       SCM newright  = SCM_EOL;
167       for (SCM s = right ; gh_pair_p (s) ; s =gh_cdr (s))
168         {
169           if (unsmob_item (gh_car (s))->column_l () == mincol)
170             newright = gh_cons (gh_car (s), newright);
171         }
172
173       me->set_grob_property ("right-items", newright);
174     }
175   
176   if (!mincol)
177     {
178       /*
179       int r = Paper_column::rank_i (dynamic_cast<Item*>(me)->column_l ());
180       programming_error (_f("Spacing wish column %d has no right item.", r));
181       */
182
183       return 0;
184     }
185
186   return mincol;
187 }
188
189 /**
190    Correct for optical illusions. See [Wanske] p. 138. The combination
191    up-stem + down-stem should get extra space, the combination
192    down-stem + up-stem less.
193
194    TODO: have to check wether the stems are in the same staff.
195
196 */
197 void
198 Note_spacing::stem_dir_correction (Grob*me, Item * rcolumn,
199                                    Real increment,
200                                    Real * space, Real *fixed)  
201 {
202   Drul_array<Direction> stem_dirs(CENTER,CENTER);
203   Drul_array<Interval> stem_posns;
204   Drul_array<Interval> head_posns;  
205   Drul_array<SCM> props(me->get_grob_property ("left-items"),
206                         me->get_grob_property ("right-items"));
207
208   Drul_array<Grob*> beams_drul(0,0);
209   Real correction = 0.0;
210   
211   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
212   Interval intersect;
213   Interval bar_xextent;
214   Interval bar_yextent;  
215   
216   bool correct = true;
217   Direction d = LEFT;
218   bool acc_right = false;
219   
220   do
221     {
222       for (SCM  s = props[d]; gh_pair_p (s); s = gh_cdr (s))
223         {
224           Item * it= dynamic_cast<Item*> (unsmob_grob (gh_car(s)));
225
226           if (d == RIGHT)
227             acc_right = acc_right || Note_column::accidentals (it);
228           
229           Grob *stem = Note_column::stem_l (it);
230
231           if (!stem)
232             {
233               if (d == RIGHT && Separation_item::has_interface (it))
234                 {
235                   if (it->column_l () != rcolumn)
236                     {
237                       it = it->find_prebroken_piece (rcolumn->break_status_dir ());
238                     }
239                   
240                   Grob *last = Staff_spacing::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
241
242                   if (last)
243                     bar_yextent = Staff_spacing::bar_y_positions (last);
244
245                   break;
246                 }
247
248               return ;
249             }
250           
251           if(Stem::invisible_b (stem))
252             {
253               return ;
254             }
255
256           beams_drul[d] = Stem::beam_l (stem);
257             
258           
259           Direction sd = Stem::get_direction (stem);
260           if (stem_dirs[d] && stem_dirs[d] != sd)
261             {
262               return ; 
263             }
264           stem_dirs[d] = sd;
265
266           /*
267             Correction doesn't seem appropriate  when there is a large flag
268             hanging from the note.
269            */
270           if (d == LEFT
271               && Stem::duration_log (stem) > 2  && !Stem::beam_l (stem))
272             {
273
274               return;
275             }
276           
277
278           
279           Interval hp  = Stem::head_positions (stem);
280           Real chord_start = hp[sd];      
281           Real stem_end = Stem::stem_end_position (stem);
282           
283           stem_posns[d] = Interval(chord_start<?stem_end, chord_start>? stem_end);
284           head_posns[d].unite (hp);
285         }
286     }
287   while (flip (&d) != LEFT);
288   
289
290   /*
291     don't correct if accidentals are sticking out of the right side.
292
293   */
294   if (acc_right)
295     return ;
296
297   if (!bar_yextent.empty_b())
298     {
299       stem_dirs[RIGHT] = - stem_dirs[LEFT];
300       stem_posns[RIGHT] = bar_yextent;
301     }
302   
303   if (correct &&stem_dirs[LEFT] *stem_dirs[RIGHT] == -1)
304     {
305       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
306         {
307           /*
308             this is a knee: maximal correction.
309           */
310           
311           correction = increment* stem_dirs[LEFT];
312           *fixed += increment* stem_dirs[LEFT];
313         }
314       else
315         {
316           intersect = stem_posns[LEFT];  
317           intersect.intersect(stem_posns[RIGHT]);
318           correct = correct && !intersect.empty_b ();
319
320           if (!correct)
321             return;
322           
323           correction = abs (intersect.length ());         
324
325       
326           /*
327             Ugh. 7 is hardcoded.
328           */
329           correction = (correction/7) <? 1.0;
330           correction *= stem_dirs[LEFT] ;
331           correction *= gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
332
333           if (!bar_yextent.empty_b())
334             {
335               correction *= 0.5;
336             }
337         }
338     }
339   else if (correct && stem_dirs[LEFT] *stem_dirs[RIGHT] == UP)
340     {
341       /*
342         Correct for the following situation:
343
344          X      X
345         |      | 
346         |      |
347         |   X  |
348         |  |   |
349         ========
350
351            ^ move the center one to the left.
352         
353
354         this effect seems to be much more subtle than the
355         stem-direction stuff (why?), and also does not scale with the
356         difference in stem length.
357         
358        */
359
360       
361       Interval hp = head_posns[LEFT];
362       hp.intersect  (head_posns[RIGHT]);
363       if (!hp.empty_b())
364         return ;
365
366       Direction lowest =
367         (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
368
369       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP] ;
370       Real corr = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
371       corr =  (delta <= 1) ? 0.0 : 0.25;
372       
373       correction=  -lowest * corr ;
374     }
375
376   if (!bar_xextent.empty_b())
377     correction += - bar_xextent[LEFT];
378
379   *space += correction;
380 }
381  
382
383
384
385 ADD_INTERFACE (Note_spacing,"note-spacing-interface",
386   "",
387   "left-items right-items stem-spacing-correction");
388