]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
release commit
[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 "output-def.hh"
22
23
24 /*
25
26 TODO: detect hshifts due to collisions, and account for them in
27 spacing?
28
29 */ 
30
31 void
32 Note_spacing::get_spacing (Grob *me, Item* right_col,
33                            Real base_space, Real increment, Real *space, Real *fixed)
34 {
35
36   Drul_array<SCM> props (me->get_property ("left-items"),
37                         me->get_property ("right-items"));
38   Direction d = LEFT;
39   Direction col_dir =  right_col->break_status_dir ();
40   Drul_array<Interval> extents;
41
42   Interval left_head_wid; 
43   do
44     {
45       for (SCM  s = props[d]; scm_is_pair (s); s = ly_cdr (s))
46         {
47           Item * it= dynamic_cast<Item*> (unsmob_grob (ly_car (s)));
48           
49           if (d == RIGHT && it->break_status_dir () != col_dir)
50             {
51               it = it -> find_prebroken_piece (col_dir);
52
53             }
54           /*
55             some kind of mismatch, eg. a note column, that is behind a
56             linebreak.
57            */
58           if (!it)
59             continue; 
60
61           Item *it_col = it->get_column ();
62           if (d == RIGHT && right_col != it_col)
63             continue;
64           
65           if (Separation_item::has_interface (it))
66             {
67               extents[d].unite (Separation_item::width (it));
68               continue;
69             }
70
71           if (d == LEFT)
72             {
73               SCM r = it->get_property ("rest");
74               Grob * g = unsmob_grob (r);
75               if (!g)
76                 g =  Note_column::first_head (it);
77
78               /*
79                 Ugh. If Stem is switched off, we don't know what the
80                 first note head will be.
81                */
82               if (g)
83                 left_head_wid = g->extent (it_col, X_AXIS);
84             }
85           
86           extents[d].unite (it->extent (it_col, X_AXIS));
87           if (d == RIGHT)
88             {
89               Grob * accs = Note_column::accidentals (it);
90               if (!accs)
91                 accs = Note_column::accidentals (it->get_parent (X_AXIS));
92               
93               if (accs)
94                 {
95                   Interval v =
96                     Accidental_placement::get_relevant_accidental_extent (accs, it_col, me);
97                     
98                   extents[d].unite (v);
99                 }
100             }
101         }
102
103       if (extents[d].is_empty ())
104         extents[d] = Interval (0,0);
105     }
106   while (flip (&d) != LEFT);
107
108
109   /*
110     We look at the width of the note head, since smaller heads get less space
111     eg. a quarter rest gets almost 0.5 ss less horizontal space than a note.
112
113     What is sticking out of the note head (eg. a flag), doesn't get
114     the full amount of space.
115
116     FIXED also includes the left part of the right object.
117   */
118   *fixed =
119     (left_head_wid.is_empty () ? increment :
120      /*
121        Size of the head:
122       */
123      (left_head_wid[RIGHT]+
124
125       /*
126        What's sticking out of the head, eg. a flag: 
127       */
128       (extents[LEFT][RIGHT] - left_head_wid[RIGHT])/2))
129
130     /*
131       What is sticking out of the right note:
132      */
133     + (extents[RIGHT].is_empty () ?  0.0 : - extents[RIGHT][LEFT] / 2);
134
135   /*
136     We don't do complicated stuff: (base_space - increment) is the
137     normal amount of white, which also determines the amount of
138     stretch. Upon (extreme) stretching, notes with accidentals should
139     stretch as much as notes without accidentals.
140    */
141   *space = (base_space - increment) + *fixed ;
142
143   if (!extents[RIGHT].is_empty ()
144       && (Item::is_breakable (right_col)
145           || right_col->original_))
146     {
147       /*
148         This is for the situation
149
150         rest | 3/4 (eol)
151
152         Since we only take half of the right-object space above, the
153         barline will bump into the notes preceding it, if the right
154         thing is big. We add the rest of the extents here:
155        */
156       
157       *space += -extents[RIGHT][LEFT] / 2;
158       *fixed += -extents[RIGHT][LEFT] / 2;
159     }
160   
161   stem_dir_correction (me, right_col, increment, space, fixed);
162 }
163
164 Item *
165 Note_spacing::left_column (Grob *me)
166 {
167   if (!me->is_live ())
168     return 0;
169   
170   return dynamic_cast<Item*> (me)->get_column ();
171 }
172
173 /*
174   Compute the column of the right-items.  This is a big function,
175 since RIGHT-ITEMS may span more columns (eg. if a clef if inserted,
176 this will add a new columns to RIGHT-ITEMS. Here we look at the
177 columns, and return the left-most. If there are multiple columns, we
178 prune RIGHT-ITEMS.
179    
180  */
181 Item *
182 Note_spacing::right_column (Grob*me)
183 {
184   if (!me->is_live ())
185     return 0;
186   
187   SCM right = me->get_property ("right-items");
188   Item *mincol = 0;
189   int min_rank = INT_MAX;
190   bool prune = false;
191   for (SCM s = right ; scm_is_pair (s) ; s = ly_cdr (s))
192     {
193       Item * ri = unsmob_item (ly_car (s));
194
195       Item * col = ri->get_column ();
196       int rank = Paper_column::get_rank (col);
197
198       if (rank < min_rank)
199         {
200           min_rank = rank;
201           if (mincol)
202             prune = true;
203
204           mincol = col;
205         }
206     }
207   
208   if (prune)
209     {
210       // I'm a lazy bum. We could do this in-place.
211       SCM newright  = SCM_EOL;
212       for (SCM s = right ; scm_is_pair (s) ; s =ly_cdr (s))
213         {
214           if (unsmob_item (ly_car (s))->get_column () == mincol)
215             newright = scm_cons (ly_car (s), newright);
216         }
217
218       me->set_property ("right-items", newright);
219     }
220   
221   if (!mincol)
222     {
223       /*
224       int r = Paper_column::get_rank (dynamic_cast<Item*>(me)->get_column ());
225       programming_error (_f ("Spacing wish column %d has no right item.", r));
226       */
227
228       return 0;
229     }
230
231   return mincol;
232 }
233
234 /**
235    Correct for optical illusions. See [Wanske] p. 138. The combination
236    up-stem + down-stem should get extra space, the combination
237    down-stem + up-stem less.
238
239    TODO: have to check wether the stems are in the same staff.
240
241 */
242 void
243 Note_spacing::stem_dir_correction (Grob*me, Item * rcolumn,
244                                    Real increment,
245                                    Real * space, Real *fixed)  
246 {
247   Drul_array<Direction> stem_dirs (CENTER,CENTER);
248   Drul_array<Interval> stem_posns;
249   Drul_array<Interval> head_posns;  
250   Drul_array<SCM> props (me->get_property ("left-items"),
251                         me->get_property ("right-items"));
252
253   Drul_array<Spanner*> beams_drul (0,0);
254   Drul_array<Grob*> stems_drul (0,0);
255   
256   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
257   Interval intersect;
258   Interval bar_xextent;
259   Interval bar_yextent;  
260   
261   bool correct_stem_dirs = true;
262   Direction d = LEFT;
263   bool acc_right = false;
264   
265   do
266     {
267       for (SCM  s = props[d]; scm_is_pair (s); s = ly_cdr (s))
268         {
269           Item * it= dynamic_cast<Item*> (unsmob_grob (ly_car (s)));
270
271           if (d == RIGHT)
272             acc_right = acc_right || Note_column::accidentals (it);
273           
274           Grob *stem = Note_column::get_stem (it);
275
276           if (!stem || !stem->is_live ())
277             {
278               if (d == RIGHT && Separation_item::has_interface (it))
279                 {
280                   if (it->get_column () != rcolumn)
281                     {
282                       it = it->find_prebroken_piece (rcolumn->break_status_dir ());
283                     }
284                   
285                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
286
287                   if (last)
288                     bar_yextent = Staff_spacing::bar_y_positions (last);
289
290                   break;
291                 }
292
293               return ;
294             }
295           
296           if (Stem::is_invisible (stem))
297             {
298               correct_stem_dirs = false;
299               continue;
300             }
301
302           stems_drul[d] = stem;
303           beams_drul[d] = Stem::get_beam (stem);
304           
305           Direction sd = Stem::get_direction (stem);
306           if (stem_dirs[d] && stem_dirs[d] != sd)
307             {
308               correct_stem_dirs = false;
309               continue;
310             }
311           stem_dirs[d] = sd;
312
313           /*
314             Correction doesn't seem appropriate  when there is a large flag
315             hanging from the note.
316            */
317           if (d == LEFT
318               && Stem::duration_log (stem) > 2  && !Stem::get_beam (stem))
319             {
320               correct_stem_dirs = false;
321             }
322           
323           Interval hp  = Stem::head_positions (stem);
324           Real chord_start = hp[sd];      
325           Real stem_end = Stem::stem_end_position (stem);
326           
327           stem_posns[d] = Interval (chord_start<?stem_end, chord_start>? stem_end);
328           head_posns[d].unite (hp);
329         }
330     }
331   while (flip (&d) != LEFT);
332   
333
334   /*
335     don't correct if accidentals are sticking out of the right side.
336   */
337   if (acc_right)
338     return ;
339
340   Real correction = 0.0;
341
342   if (!bar_yextent.is_empty ())
343     {
344       stem_dirs[RIGHT] = - stem_dirs[LEFT];
345       stem_posns[RIGHT] = bar_yextent;
346     }
347   
348   if (correct_stem_dirs && stem_dirs[LEFT] *stem_dirs[RIGHT] == -1)
349     {
350       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
351         {
352           
353           /*
354             this is a knee: maximal correction.
355           */
356           Real note_head_width = increment;
357           Grob * st = stems_drul[RIGHT];
358           Grob * head = st ? Stem::support_head (st)  : 0;
359
360           Interval head_extent;
361           if (head)
362             {
363               head_extent = head->extent (rcolumn, X_AXIS);
364
365               if (!head_extent.is_empty ())
366                 note_head_width = head_extent[RIGHT];
367
368               if (st)
369                 {
370                   Real thick = Stem::thickness (st);
371
372                   note_head_width -= thick;
373                 }
374             }
375
376           correction = note_head_width* stem_dirs[LEFT];
377           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
378           *fixed += correction;
379         }
380       else
381         {
382           intersect = stem_posns[LEFT];  
383           intersect.intersect (stem_posns[RIGHT]);
384           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
385
386           if (correct_stem_dirs)
387             {
388               correction =abs (intersect.length ());
389
390       
391               /*
392                 Ugh. 7 is hardcoded.
393               */
394               correction = (correction/7) <? 1.0;
395               correction *= stem_dirs[LEFT] ;
396               correction *=
397                 robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
398             }
399           
400           if (!bar_yextent.is_empty ())
401             {
402               correction *= 0.5;
403             }
404         }
405     }
406   else if (correct_stem_dirs && stem_dirs[LEFT] *stem_dirs[RIGHT] == UP)
407     {
408       /*
409         Correct for the following situation:
410
411          X      X
412         |      | 
413         |      |
414         |   X  |
415         |  |   |
416         ========
417
418            ^ move the center one to the left.
419         
420
421         this effect seems to be much more subtle than the
422         stem-direction stuff (why?), and also does not scale with the
423         difference in stem length.
424         
425        */
426
427       
428       Interval hp = head_posns[LEFT];
429       hp.intersect  (head_posns[RIGHT]);
430       if (!hp.is_empty ())
431         return ;
432
433       Direction lowest =
434         (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
435
436       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP] ;
437       Real corr = robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
438       corr =  (delta <= 1) ? 0.0 : 0.25;
439       
440       correction=  -lowest * corr ;
441     }
442
443   *space += correction;
444
445   /* there used to be a correction for bar_xextent () here, but
446      it's unclear what that was good for ?
447   */
448
449 }
450  
451
452
453
454 ADD_INTERFACE (Note_spacing,"note-spacing-interface",
455   "This object calculates spacing wishes for individual voices.",
456   "left-items right-items stem-spacing-correction knee-spacing-correction");
457