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