]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
95ba49aa64a7a271f06dd4261dddb62ace9e0cbf
[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->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 void
201 Note_spacing::stem_dir_correction (Grob*me, Item * rcolumn,
202                                    Real increment,
203                                    Real * space, Real *fixed)  
204 {
205   Drul_array<Direction> stem_dirs(CENTER,CENTER);
206   Drul_array<Interval> stem_posns;
207   Drul_array<Interval> head_posns;  
208   Drul_array<SCM> props(me->get_grob_property ("left-items"),
209                         me->get_grob_property ("right-items"));
210
211   Drul_array<Grob*> beams_drul(0,0);
212   Real correction = 0.0;
213   
214   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
215   Interval intersect;
216   Interval bar_xextent;
217   Interval bar_yextent;  
218   
219   bool correct = true;
220   Direction d = LEFT;
221   bool acc_right = false;
222   
223   do
224     {
225       for (SCM  s = props[d]; gh_pair_p (s); s = gh_cdr (s))
226         {
227           Item * it= dynamic_cast<Item*> (unsmob_grob (gh_car(s)));
228
229           if (d == RIGHT)
230             acc_right = acc_right || Note_column::accidentals (it);
231           
232           Grob *stem = Note_column::stem_l (it);
233
234           if (!stem)
235             {
236               if (d == RIGHT && Separation_item::has_interface (it))
237                 {
238                   if (it->column_l () != rcolumn)
239                     {
240                       it = it->find_prebroken_piece (rcolumn->break_status_dir ());
241                     }
242                   
243                   Grob *last = Staff_spacing::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
244
245                   if (last)
246                     bar_yextent = Staff_spacing::bar_y_positions (last);
247
248                   break;
249                 }
250
251               return ;
252             }
253           
254           if(Stem::invisible_b (stem))
255             {
256               return ;
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               return ; 
266             }
267           stem_dirs[d] = sd;
268
269           /*
270             Correction doesn't seem appropriate  when there is a large flag
271             hanging from the note.
272            */
273           if (d == LEFT
274               && Stem::duration_log (stem) > 2  && !Stem::beam_l (stem))
275             {
276
277               return;
278             }
279           
280
281           
282           Interval hp  = Stem::head_positions (stem);
283           Real chord_start = hp[sd];      
284           Real stem_end = Stem::stem_end_position (stem);
285           
286           stem_posns[d] = Interval(chord_start<?stem_end, chord_start>? stem_end);
287           head_posns[d].unite (hp);
288         }
289     }
290   while (flip (&d) != LEFT);
291   
292
293   /*
294     don't correct if accidentals are sticking out of the right side.
295
296   */
297   if (acc_right)
298     return ;
299
300   if (!bar_yextent.empty_b())
301     {
302       stem_dirs[RIGHT] = - stem_dirs[LEFT];
303       stem_posns[RIGHT] = bar_yextent;
304     }
305   
306   if (correct &&stem_dirs[LEFT] *stem_dirs[RIGHT] == -1)
307     {
308       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
309         {
310           /*
311             this is a knee: maximal correction.
312           */
313           
314           correction = increment* stem_dirs[LEFT];
315           *fixed += 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;
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 ;
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   *space += correction;
383 }
384  
385
386
387
388 ADD_INTERFACE (Note_spacing,"note-spacing-interface",
389   "",
390   "left-items right-items stem-spacing-correction");
391