]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
e7256e8bbe038858ffbb6bdb2269fe9fb4e4b5cc
[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               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)->get_column ();
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->get_column ();
156       int rank = Paper_column::get_rank (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))->get_column () == 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::get_rank (dynamic_cast<Item*>(me)->get_column ());
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::get_stem (it);
235
236           if (!stem)
237             {
238               if (d == RIGHT && Separation_item::has_interface (it))
239                 {
240                   if (it->get_column () != 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::get_beam (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::get_beam (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           correction *= gh_scm2double (me->get_grob_property ("knee-spacing-correction"));
318           *fixed += correction;
319         }
320       else
321         {
322           intersect = stem_posns[LEFT];  
323           intersect.intersect(stem_posns[RIGHT]);
324           correct = correct && !intersect.empty_b ();
325
326           if (!correct)
327             return;
328           
329           correction = abs (intersect.length ());         
330
331       
332           /*
333             Ugh. 7 is hardcoded.
334           */
335           correction = (correction/7) <? 1.0;
336           correction *= stem_dirs[LEFT] ;
337           correction *= gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
338
339           if (!bar_yextent.empty_b())
340             {
341               correction *= 0.5;
342             }
343         }
344     }
345   else if (correct && stem_dirs[LEFT] *stem_dirs[RIGHT] == UP)
346     {
347       /*
348         Correct for the following situation:
349
350          X      X
351         |      | 
352         |      |
353         |   X  |
354         |  |   |
355         ========
356
357            ^ move the center one to the left.
358         
359
360         this effect seems to be much more subtle than the
361         stem-direction stuff (why?), and also does not scale with the
362         difference in stem length.
363         
364        */
365
366       
367       Interval hp = head_posns[LEFT];
368       hp.intersect  (head_posns[RIGHT]);
369       if (!hp.empty_b())
370         return ;
371
372       Direction lowest =
373         (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
374
375       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP] ;
376       Real corr = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
377       corr =  (delta <= 1) ? 0.0 : 0.25;
378       
379       correction=  -lowest * corr ;
380     }
381
382   if (!bar_xextent.empty_b())
383     correction += - bar_xextent[LEFT];
384
385   *space += correction;
386 }
387  
388
389
390
391 ADD_INTERFACE (Note_spacing,"note-spacing-interface",
392   "",
393   "left-items right-items stem-spacing-correction knee-spacing-correction");
394