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