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