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