]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
release: 1.5.36
[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
19 bool
20 Note_spacing::has_interface (Grob* g)
21 {
22   return g && g->has_interface (ly_symbol2scm ("note-spacing-interface"));
23 }
24
25 Real
26 Note_spacing::get_spacing (Grob *me)
27 {
28   Drul_array<SCM> props(me->get_grob_property ("left-items"),
29                         me->get_grob_property ("right-items"));
30   Direction d = LEFT;
31   Drul_array<Interval> extents;
32   do
33     {
34       for (SCM  s = props[d]; gh_pair_p (s); s = gh_cdr (s))
35         {
36           Item * it= dynamic_cast<Item*> (unsmob_grob (gh_car(s)));
37           extents[d].unite (it->extent (it->column_l (), X_AXIS));
38
39           if (d == RIGHT)
40             {
41               Grob * accs = Note_column::accidentals (it);
42               if (!accs)
43                 accs = Note_column::accidentals (it->get_parent (X_AXIS));
44               
45               if (accs)
46                 extents[d].unite (accs->extent (it->column_l (), X_AXIS));
47             }
48         }
49
50       if (extents[d].empty_b ())
51         extents[d] = Interval (0,0);
52     }
53   while (flip (&d) != LEFT);
54
55   /*
56     
57     What's sticking out at the left of the right side has less
58     influence.
59
60   */
61   Real dx= extents[LEFT][RIGHT] - 0.5 * extents[RIGHT][LEFT];
62   return dx;
63 }
64
65 Item *
66 Note_spacing::left_column (Grob *me)
67 {
68   if (me->immutable_property_alist_ == SCM_EOL)
69     return 0;
70   
71   return dynamic_cast<Item*> (me)->column_l ();
72 }
73
74 /*
75   Compute the column of the right-items.  This is a big function,
76 since RIGHT-ITEMS may span more columns (eg. if a clef if inserted,
77 this will add a new columns to RIGHT-ITEMS. Here we look at the
78 columns, and return the left-most. If there are multiple columns, we
79 prune RIGHT-ITEMS.
80    
81  */
82 Item *
83 Note_spacing::right_column (Grob*me)
84 {
85   /*
86     ugh. should have generic is_live() method?
87    */
88   if (me->immutable_property_alist_ == SCM_EOL)
89     return 0;
90   
91   SCM right = me->get_grob_property ("right-items");
92   Item *mincol = 0;
93   int min_rank = INT_MAX;
94   bool prune = false;
95   for (SCM s = right ; gh_pair_p (s) ; s = gh_cdr (s))
96     {
97       Item * ri = unsmob_item (gh_car (s));
98
99       Item * col = ri->column_l ();
100       int rank = Paper_column::rank_i (col);
101
102       if (rank < min_rank)
103         {
104           min_rank = rank;
105           if (mincol)
106             prune = true;
107
108           mincol = col;
109         }
110     }
111   
112   if (prune)
113     {
114       // I'm a lazy bum. We could do this in-place.
115       SCM newright  = SCM_EOL;
116       for (SCM s = right ; gh_pair_p (s) ; s =gh_cdr (s))
117         {
118           if (unsmob_item (gh_car (s))->column_l () == mincol)
119             newright = gh_cons (gh_car (s), newright);
120         }
121
122       me->set_grob_property ("right-items", newright);
123     }
124   
125   if (!mincol)
126     {
127       /*
128       int r = Paper_column::rank_i (dynamic_cast<Item*>(me)->column_l ());
129       programming_error (_f("Spacing wish column %d has no right item.", r));
130       */
131
132       return 0;
133     }
134
135   return mincol;
136 }
137
138 /**
139    Correct for optical illusions. See [Wanske] p. 138. The combination
140    up-stem + down-stem should get extra space, the combination
141    down-stem + up-stem less.
142
143    TODO: have to check wether the stems are in the same staff.
144
145    TODO: also correct for bar lines in RIGHT-ITEMS.  Should check if
146    the barline is the leftmost object of the break alignment.
147
148 */
149 Real
150 Note_spacing::stem_dir_correction (Grob*me) 
151 {
152   Drul_array<Direction> stem_dirs(CENTER,CENTER);
153   Drul_array<Interval> stem_posns;
154   Drul_array<Interval> head_posns;  
155   Drul_array<SCM> props(me->get_grob_property ("left-items"),
156                         me->get_grob_property ("right-items"));
157
158   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
159   Interval intersect;
160   bool correct = true;
161   Direction d = LEFT;
162   bool acc_right = false;
163   
164   do
165     {
166       for (SCM  s = props[d]; gh_pair_p (s); s = gh_cdr (s))
167         {
168           Item * it= dynamic_cast<Item*> (unsmob_grob (gh_car(s)));
169
170           if (d == RIGHT)
171             acc_right = acc_right || Note_column::accidentals (it);
172           
173           Grob *stem = Note_column::stem_l (it);
174
175           if (!stem || Stem::invisible_b (stem))
176             {
177               correct = false;
178               goto exit_loop ;
179             }
180
181           Direction sd = Stem::get_direction (stem);
182           if (stem_dirs[d] && stem_dirs[d] != sd)
183             {
184               correct = false;
185               goto exit_loop;
186             }
187           stem_dirs[d] = sd;
188
189           Interval hp  = Stem::head_positions (stem);
190           Real chord_start = hp[sd];      
191           Real stem_end = Stem::stem_end_position (stem);
192           
193           stem_posns[d] = Interval(chord_start<?stem_end, chord_start>? stem_end);
194           head_posns[d].unite (hp);
195         }
196     }
197   while (flip (&d) != LEFT);
198   
199
200   /*
201     don't correct if accidentals are sticking out of the right side.
202
203   */
204   if (acc_right)
205     return 0.0;
206
207   
208   if (correct && stem_dirs[LEFT] *stem_dirs[RIGHT] == -1)
209     {
210       intersect = stem_posns[LEFT];  
211       intersect.intersect(stem_posns[RIGHT]);
212       correct = correct && !intersect.empty_b ();
213
214       if (!correct)
215         return 0.0;
216       /*
217       Ugh. 7 is hardcoded.
218     */
219       Real correction = abs (intersect.length ());
220       correction = (correction/7) <? 1.0;
221       correction *= stem_dirs[LEFT] ;
222       correction *= gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
223       return correction;
224     }
225   else if (correct)
226     {
227       /*
228         Correct for the following situation:
229
230          X      X
231         |      | 
232         |      |
233         |   X  |
234         |  |   |
235         ========
236
237            ^ move the center one to the left.
238         
239
240         this effect seems to be much more subtle than the
241         stem-direction stuff (why?), and also does not scale with the
242         difference in stem length.
243         
244        */
245
246       
247       Interval hp = head_posns[LEFT];
248       hp.intersect  (head_posns[RIGHT]);
249       if (!hp.empty_b())
250         return 0.0;
251
252       Direction lowest =
253         (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
254
255       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP] ;
256       Real corr = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
257       corr =  (delta <= 1) ? 0.0 : 0.25;
258       
259       return -lowest * corr ;
260     }
261
262   
263  exit_loop:
264   return 0.0;
265 }
266