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