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