]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
* lily/accidental-placement.cc (split_accidentals): new function
[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->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::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                 {
82                   Interval v =
83                     Accidental_placement::get_relevant_accidental_extent (accs, it_col, me);
84                     
85                   extents[d].unite (v);
86                 }
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   stem_dir_correction (me, right_col, increment, space, fixed);
122 }
123
124 Item *
125 Note_spacing::left_column (Grob *me)
126 {
127   if (!me->live())
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   if (!me->live())
145     return 0;
146   
147   SCM right = me->get_grob_property ("right-items");
148   Item *mincol = 0;
149   int min_rank = INT_MAX;
150   bool prune = false;
151   for (SCM s = right ; gh_pair_p (s) ; s = gh_cdr (s))
152     {
153       Item * ri = unsmob_item (gh_car (s));
154
155       Item * col = ri->column_l ();
156       int rank = Paper_column::rank_i (col);
157
158       if (rank < min_rank)
159         {
160           min_rank = rank;
161           if (mincol)
162             prune = true;
163
164           mincol = col;
165         }
166     }
167   
168   if (prune)
169     {
170       // I'm a lazy bum. We could do this in-place.
171       SCM newright  = SCM_EOL;
172       for (SCM s = right ; gh_pair_p (s) ; s =gh_cdr (s))
173         {
174           if (unsmob_item (gh_car (s))->column_l () == mincol)
175             newright = gh_cons (gh_car (s), newright);
176         }
177
178       me->set_grob_property ("right-items", newright);
179     }
180   
181   if (!mincol)
182     {
183       /*
184       int r = Paper_column::rank_i (dynamic_cast<Item*>(me)->column_l ());
185       programming_error (_f("Spacing wish column %d has no right item.", r));
186       */
187
188       return 0;
189     }
190
191   return mincol;
192 }
193
194 /**
195    Correct for optical illusions. See [Wanske] p. 138. The combination
196    up-stem + down-stem should get extra space, the combination
197    down-stem + up-stem less.
198
199    TODO: have to check wether the stems are in the same staff.
200
201 */
202 void
203 Note_spacing::stem_dir_correction (Grob*me, Item * rcolumn,
204                                    Real increment,
205                                    Real * space, Real *fixed)  
206 {
207   Drul_array<Direction> stem_dirs(CENTER,CENTER);
208   Drul_array<Interval> stem_posns;
209   Drul_array<Interval> head_posns;  
210   Drul_array<SCM> props(me->get_grob_property ("left-items"),
211                         me->get_grob_property ("right-items"));
212
213   Drul_array<Grob*> beams_drul(0,0);
214   Real correction = 0.0;
215   
216   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
217   Interval intersect;
218   Interval bar_xextent;
219   Interval bar_yextent;  
220   
221   bool correct = true;
222   Direction d = LEFT;
223   bool acc_right = false;
224   
225   do
226     {
227       for (SCM  s = props[d]; gh_pair_p (s); s = gh_cdr (s))
228         {
229           Item * it= dynamic_cast<Item*> (unsmob_grob (gh_car(s)));
230
231           if (d == RIGHT)
232             acc_right = acc_right || Note_column::accidentals (it);
233           
234           Grob *stem = Note_column::stem_l (it);
235
236           if (!stem)
237             {
238               if (d == RIGHT && Separation_item::has_interface (it))
239                 {
240                   if (it->column_l () != rcolumn)
241                     {
242                       it = it->find_prebroken_piece (rcolumn->break_status_dir ());
243                     }
244                   
245                   Grob *last = Staff_spacing::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
246
247                   if (last)
248                     bar_yextent = Staff_spacing::bar_y_positions (last);
249
250                   break;
251                 }
252
253               return ;
254             }
255           
256           if(Stem::invisible_b (stem))
257             {
258               return ;
259             }
260
261           beams_drul[d] = Stem::beam_l (stem);
262             
263           
264           Direction sd = Stem::get_direction (stem);
265           if (stem_dirs[d] && stem_dirs[d] != sd)
266             {
267               return ; 
268             }
269           stem_dirs[d] = sd;
270
271           /*
272             Correction doesn't seem appropriate  when there is a large flag
273             hanging from the note.
274            */
275           if (d == LEFT
276               && Stem::duration_log (stem) > 2  && !Stem::beam_l (stem))
277             {
278
279               return;
280             }
281           
282
283           
284           Interval hp  = Stem::head_positions (stem);
285           Real chord_start = hp[sd];      
286           Real stem_end = Stem::stem_end_position (stem);
287           
288           stem_posns[d] = Interval(chord_start<?stem_end, chord_start>? stem_end);
289           head_posns[d].unite (hp);
290         }
291     }
292   while (flip (&d) != LEFT);
293   
294
295   /*
296     don't correct if accidentals are sticking out of the right side.
297
298   */
299   if (acc_right)
300     return ;
301
302   if (!bar_yextent.empty_b())
303     {
304       stem_dirs[RIGHT] = - stem_dirs[LEFT];
305       stem_posns[RIGHT] = bar_yextent;
306     }
307   
308   if (correct &&stem_dirs[LEFT] *stem_dirs[RIGHT] == -1)
309     {
310       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
311         {
312           /*
313             this is a knee: maximal correction.
314           */
315           
316           correction = increment* stem_dirs[LEFT];
317           *fixed += increment* stem_dirs[LEFT];
318         }
319       else
320         {
321           intersect = stem_posns[LEFT];  
322           intersect.intersect(stem_posns[RIGHT]);
323           correct = correct && !intersect.empty_b ();
324
325           if (!correct)
326             return;
327           
328           correction = abs (intersect.length ());         
329
330       
331           /*
332             Ugh. 7 is hardcoded.
333           */
334           correction = (correction/7) <? 1.0;
335           correction *= stem_dirs[LEFT] ;
336           correction *= gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
337
338           if (!bar_yextent.empty_b())
339             {
340               correction *= 0.5;
341             }
342         }
343     }
344   else if (correct && stem_dirs[LEFT] *stem_dirs[RIGHT] == UP)
345     {
346       /*
347         Correct for the following situation:
348
349          X      X
350         |      | 
351         |      |
352         |   X  |
353         |  |   |
354         ========
355
356            ^ move the center one to the left.
357         
358
359         this effect seems to be much more subtle than the
360         stem-direction stuff (why?), and also does not scale with the
361         difference in stem length.
362         
363        */
364
365       
366       Interval hp = head_posns[LEFT];
367       hp.intersect  (head_posns[RIGHT]);
368       if (!hp.empty_b())
369         return ;
370
371       Direction lowest =
372         (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
373
374       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP] ;
375       Real corr = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
376       corr =  (delta <= 1) ? 0.0 : 0.25;
377       
378       correction=  -lowest * corr ;
379     }
380
381   if (!bar_xextent.empty_b())
382     correction += - bar_xextent[LEFT];
383
384   *space += correction;
385 }
386  
387
388
389
390 ADD_INTERFACE (Note_spacing,"note-spacing-interface",
391   "",
392   "left-items right-items stem-spacing-correction");
393