]> git.donarmstrong.com Git - lilypond.git/blob - lily/spacing-spanner.cc
release: 1.5.3
[lilypond.git] / lily / spacing-spanner.cc
1 /*   
2   spacing-spanner.cc --  implement Spacing_spanner
3   
4   source file of the GNU LilyPond music typesetter
5   
6   (c) 1999--2001 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   
8  */
9
10 #include "spacing-spanner.hh"
11 #include "paper-column.hh"
12 #include "dimensions.hh"
13 #include "paper-def.hh"
14 #include "warn.hh"
15 #include "paper-score.hh"
16 #include "line-of-score.hh"
17 #include "misc.hh"
18
19 void
20 Spacing_spanner::set_interface (Grob*me)
21 {
22   me->set_extent_callback (SCM_EOL, X_AXIS);
23   me->set_extent_callback (SCM_EOL, Y_AXIS) ; 
24 }
25
26 #if 0  
27 struct Note_run
28 {
29   Array<int> idxes;
30   int start, end;
31   Moment duration;
32   int count;
33 };
34
35 int
36 column_compare (Grob  *const &t1, Grob *const &t2)
37 {
38   return Moment::compare (Paper_column::when_mom (t1),
39                           Paper_column::when_mom (t2));
40 }
41
42
43 Note_run
44 run_length (Moment dt, int i, Array<Moment> const &moms,
45             Link_array<Note_run> runs)
46 {
47   int k = 0;
48   Array<int> idxes;
49
50   idxes.push (i);
51   while (1)
52     {
53       Moment next = moms[i] + dt;
54       while (i < moms.size () && moms[i] < next)
55         i++;
56       if (i == moms.size () || moms[i] != next)
57         break;
58
59       idxes.push (i);
60       k++;
61     }
62
63   Moment dur = idxes.size ()
64 }
65
66 void
67 find_runs (Grob*me, Link_array<Grob> cols) 
68 {
69   Link_array<Grob> filter_cols;
70   Array<Moment> col_moments;
71   for (int i = 0; i < cols.size (); i++)
72     {
73       Moment w =  Paper_column::when_mom (cols[i]);
74       
75       if (!w.grace_mom_ && Paper_column::musical_b (cols[i]))
76         {
77           filter_cols.push (cols[i]);
78           col_moments.push (w);
79         }
80     }
81
82   Moment end_mom = col_moments.top ();
83   for (int i = 0; i < col_moments.size () ; i++)
84     {
85       for (int j = i+1; j < col_moments.size (); j++)
86         {
87           Moment dt = Paper_column::col_momentsfilter_cols 
88         }
89     }
90 }
91 #endif  
92
93 /*
94
95   The algorithm is partly taken from :
96
97   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
98   OSU-CISRC-10/87-TR35, Department of Computer and Information
99   Science, The Ohio State University, 1987.
100
101   TOO HAIRY.
102
103   TODO: write comments 
104   
105  */
106 void
107 Spacing_spanner::do_measure (Grob*me, Link_array<Grob> const & cols) 
108 {
109   Moment shortest;
110   Moment mean_shortest;
111
112   /*
113     space as if this duration  is present. 
114    */
115   Moment base_shortest_duration = *unsmob_moment (me->get_grob_property ("maximum-duration-for-spacing"));
116   shortest.set_infinite (1);
117
118   int n = 0;
119   for (int i =0 ; i < cols.size (); i++)  
120     {
121       if (Paper_column::musical_b (cols[i]))
122         {
123           Moment *when = unsmob_moment (cols[i]->get_grob_property  ("when"));
124
125           /*
126             ignore grace notes for shortest notes.
127            */
128           if (when && when->grace_mom_)
129             continue;
130           
131           SCM  st = cols[i]->get_grob_property ("shortest-starter-duration");
132           Moment this_shortest = *unsmob_moment (st);
133           shortest = shortest <? this_shortest;
134           if (!mean_shortest.main_part_.infty_b ())
135             {
136               n++;
137               mean_shortest += this_shortest;
138             }
139         }
140     }
141   mean_shortest /= n;
142
143   Array<Spring> springs;
144   for (int i= 0; i < cols.size () - 1; i++)
145     {
146       Item * l = dynamic_cast<Item*> (cols[i]);
147       Item * r =  dynamic_cast<Item*> (cols[i+1]);
148       Item * lb = dynamic_cast<Item*> (l->find_prebroken_piece (RIGHT));
149       Item * rb = dynamic_cast<Item*> (r->find_prebroken_piece (LEFT));
150
151       Item* combinations[4][2]={{l,r}, {lb,r}, {l,rb},{lb,rb}};
152
153
154       /*
155         left refers to the space that is associated with items of the left column, so you have
156
157           LC  <- left_space -><- right_space -> RC
158               <-    total space              ->
159               
160
161         typically, right_space is non-zero when there are
162         accidentals in RC
163           
164        */
165       for (int j=0; j < 4; j++)
166         {
167           Paper_column * lc = dynamic_cast<Paper_column*> (combinations[j][0]);
168           Paper_column *rc = dynamic_cast<Paper_column*> (combinations[j][1]);
169           if (!lc || !rc)
170             continue;
171
172           Spring s;
173           s.item_l_drul_[LEFT] = lc;
174           s.item_l_drul_[RIGHT] = rc;
175           
176           SCM hint = lc->get_grob_property ("extra-space");
177           SCM next_hint = rc->get_grob_property ("extra-space");
178           SCM stretch_hint = lc->get_grob_property ("stretch-distance");
179           SCM next_stretch_hint = rc->get_grob_property ("stretch-distance");     
180
181           Real left_distance = 0;
182           if (gh_pair_p (hint))
183             {
184               left_distance = gh_scm2double (gh_cdr (hint)); 
185             }
186            // 2nd condition should be (i+1 < col_count ()), ie. not the last column in score.  FIXME
187           else if (!Paper_column::musical_b (lc) && i+1 < cols.size ()) 
188             {
189               left_distance= default_bar_spacing (me,lc,rc,shortest <? base_shortest_duration);
190             }
191           else if (Paper_column::musical_b ( lc))
192             {
193               left_distance  = note_spacing (me,lc, rc, shortest <? base_shortest_duration);
194             }
195           else
196               programming_error ("uninitialised left_distance");
197           
198           s.distance_f_ = left_distance;
199
200           /*
201             Only do tight spaces *after* barlines (breakable columns),
202             not before.
203
204             We want the space before barline to be like the note
205             spacing in the measure.
206           */
207           SCM sfac =lc->get_grob_property ("space-factor");
208           if (gh_number_p (lc->get_grob_property ("column-space-strength"))
209               && (Item::breakable_b (lc) || lc->original_l_))
210             {
211               s.strength_f_ =
212                 gh_scm2double (lc->get_grob_property ("column-space-strength"));
213             }
214           else if (gh_number_p (sfac))
215             left_distance *= gh_scm2double (sfac);
216
217           
218           Real right_dist = 0.0;
219           if (gh_pair_p (next_hint))
220             {
221               right_dist += - gh_scm2double (gh_car (next_hint));
222             }
223           else
224             {
225               Interval ext (rc->extent (rc, X_AXIS));
226               right_dist =  ext.empty_b () ? 0.0 : - ext [LEFT];
227             }
228
229           /*
230             don't want to create too much extra space for accidentals
231           */
232           if (Paper_column::musical_b (rc))
233            {
234               if (to_boolean (rc->get_grob_property ("contains-grace")))
235                 right_dist *= gh_scm2double (rc->get_grob_property ("before-grace-spacing-factor")); // fixme.
236               else
237                 right_dist *= gh_scm2double (lc->get_grob_property ("before-musical-spacing-factor"));
238            }
239
240           s.distance_f_ = left_distance + right_dist;
241             
242           Real stretch_dist = 0.;
243           if (gh_number_p (stretch_hint))
244             stretch_dist += gh_scm2double (stretch_hint);
245           else
246             stretch_dist += left_distance;
247           
248           if (gh_pair_p (next_stretch_hint))
249             // see regtest spacing-tight
250             stretch_dist += - gh_scm2double (gh_car (next_stretch_hint));
251           else
252             stretch_dist += right_dist;
253
254           if (s.distance_f_ <0)
255             {
256               programming_error ("Negative dist, setting to 1.0 PT");
257               s.distance_f_ = 1.0;
258             }
259           if (stretch_dist == 0.0)
260             {
261               /*
262                 \bar "".  We give it 0 space, with high strength. 
263                */
264               s.strength_f_ = 20.0; 
265             }
266           else
267             s.strength_f_ /= stretch_dist;
268           
269           springs.push (s);
270         }
271     }
272
273   Spacing_spanner::stretch_to_regularity (me, &springs, cols);
274   for (int i=springs.size (); i --;)
275     springs[i].add_to_cols ();
276 }
277
278 /*
279   Look at COLS, searching for columns that have 'regular-distance-to
280   set. A sequence of columns that have this property set should have
281   an equal distance (an equispaced run). Extract the projected
282   distance from SPRINGS, and scale SPRINGS for the equispaced run, to the
283   widest space necessary.
284
285
286   TODO:
287   
288   -- inefficient code; maybe it is easier to twiddle with the springs
289   after they've become grob properties (ie. have their
290   minimum-distances set)
291
292   -- does not adjust strength field of the springs very well: result
293   awkward spacing at the start of a line. (?)
294
295   -- will be confused when there are multiple equispaced runs in a measure.
296
297   -- dealing with springs for line breaks is a little tricky; in any
298   case, we will only space per measure.
299
300   -- we scale to actual distances, not to optical effects. Eg. if the
301   equispaced run contains optical corrections, then the scaling will
302   cancel those.
303
304   -- Regular_spacing_engraver doesn't mark the first column of the
305   next bar, making the space before a barline too short, in this case
306
307
308        x<- 16ths--> x(8th)
309        x(8th)       x(8th)      <- equispaced run.      
310   
311 */
312
313 void
314 Spacing_spanner::stretch_to_regularity (Grob *me,
315                                         Array<Spring> * springs,
316                                         Link_array<Grob> const & cols)
317 {
318   /*
319     Find the starting column of the run. REGULAR-DISTANCE-TO points
320     back to a previous column, so we look ahead to find a column
321     pointing back to the first one.
322     
323    */
324   Grob    * first_regular_spaced_col = 0;
325   for (int i = 0 ;  i <  cols.size () && !first_regular_spaced_col; i++)
326     {
327       SCM rdt = cols[i]->get_grob_property ("regular-distance-to");
328       if (cols.find_l (unsmob_grob (rdt)))
329         first_regular_spaced_col = unsmob_grob (rdt);
330     }
331   for (int i = springs->size ();  i-- ;)
332     springs->elem (i).set_to_cols ();
333   
334   int i;
335   for (i = 0; i < springs->size ()
336          && springs->elem (i).item_l_drul_[RIGHT] != first_regular_spaced_col;
337        i++)
338     ;
339
340
341   if (i==springs->size ())
342     return ;
343     
344   Real maxdist = 0.0;
345   Real dist  =0.0;
346   Grob *last_col = first_regular_spaced_col;
347   Grob *last_regular_spaced_col = first_regular_spaced_col;
348   
349
350   /*
351     find the max distance for this run. 
352    */
353   for (int j = i;  j < springs->size (); j++)
354     {
355       Spring *s = &(springs->elem_ref (j));
356       if (s->item_l_drul_[LEFT] != last_col)
357         continue;
358       
359       dist += s->distance_f_;
360
361       last_col = s->item_l_drul_[RIGHT];
362       SCM rdt = last_col->get_grob_property ("regular-distance-to");
363       if (unsmob_grob (rdt) == last_regular_spaced_col)
364         {
365           maxdist = maxdist >? dist;
366           dist = 0.0;
367           last_regular_spaced_col = last_col;
368         }
369
370     }
371
372   /*
373     Scale the springs
374    */
375   dist =0.0;
376   last_col =  first_regular_spaced_col;
377   last_regular_spaced_col = first_regular_spaced_col;
378   for (int j = i;   j < springs->size (); j++)
379     {
380       Spring *s = &springs->elem_ref (j);
381       if (s->item_l_drul_[LEFT] != last_col)
382         continue;
383       dist += s->distance_f_;
384
385       last_col = s->item_l_drul_[RIGHT];
386       SCM rdt = last_col->get_grob_property ("regular-distance-to");
387       if (unsmob_grob (rdt) == last_regular_spaced_col)
388         {
389           do {
390             springs->elem_ref (i).distance_f_ *= maxdist / dist;
391             springs->elem_ref (i).strength_f_ *= dist / maxdist;            
392           } while (i++ < j);
393           last_regular_spaced_col = last_col;
394           dist =0.0;
395         }
396     }
397 }
398
399 /**
400    Do something if breakable column has no spacing hints set.
401  */
402 Real
403 Spacing_spanner::default_bar_spacing (Grob*me, Grob *lc, Grob *rc,
404                                       Moment shortest) 
405 {
406   Real symbol_distance = lc->extent (lc,X_AXIS)[RIGHT] ;
407   Real durational_distance = 0;
408   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
409
410   /*
411                 ugh should use shortest_playing distance
412   */
413   if (delta_t)
414     {
415       durational_distance =  get_duration_space (me, delta_t, shortest);
416     }
417
418   return  symbol_distance >? durational_distance;
419 }
420
421
422 /**
423   Get the measure wide ant for arithmetic spacing.
424
425   @see
426   John S. Gourlay. ``Spacing a Line of Music,'' Technical Report
427   OSU-CISRC-10/87-TR35, Department of Computer and Information Science,
428   The Ohio State University, 1987.
429
430   */
431 Real
432 Spacing_spanner::get_duration_space (Grob*me, Moment d, Moment shortest) 
433 {
434   Real log =  log_2 (shortest);
435   Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
436     - log;
437   
438   return (log_2 (d) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
439 }
440
441
442 Real
443 Spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
444                                Moment shortest) 
445 {
446   Moment shortest_playing_len = 0;
447   SCM s = lc->get_grob_property ("shortest-playing-duration");
448
449   //  SCM s = lc->get_grob_property ("mean-playing-duration");  
450   if (unsmob_moment (s))
451     shortest_playing_len = *unsmob_moment (s);
452   
453   if (! shortest_playing_len)
454     {
455       programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
456       shortest_playing_len = 1;
457     }
458   
459   if (! shortest)
460     {
461       programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
462       shortest = 1;
463     }
464   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
465   Real dist = get_duration_space (me, shortest_playing_len, shortest);
466
467
468   /*
469     ugh: 0.1 is an arbitrary distance.
470    */
471   dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_)
472     + 0.1 * (double) (delta_t.grace_mom_ / shortest_playing_len.main_part_);
473
474
475
476   /*
477     UGH: KLUDGE!
478   */
479   
480   if (delta_t > Moment (1,32))
481     dist += stem_dir_correction (me, lc,rc);
482
483
484   Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
485   Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
486
487   if (lm && rm)
488     {
489       if (lm->grace_mom_ && rm->grace_mom_)
490         dist *= 0.5;
491       else if (!rm->grace_mom_ && lm->grace_mom_)
492         dist *= 0.7;
493     }
494
495   
496   return dist;
497 }
498
499
500 /**
501    Correct for optical illusions. See [Wanske] p. 138. The combination
502    up-stem + down-stem should get extra space, the combination
503    down-stem + up-stem less.
504
505    This should be more advanced, since relative heights of the note
506    heads also influence required correction.
507
508    Also might not work correctly in case of multi voices or staff
509    changing voices
510
511    TODO: lookup correction distances?  More advanced correction?
512    Possibly turn this off?
513
514    TODO: have to check wether the stems are in the same staff.
515
516    This routine reads the DIR-LIST property of both its L and R arguments.  */
517 Real
518 Spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r) 
519 {
520   SCM dl = l->get_grob_property ("dir-list");
521   SCM dr = r->get_grob_property ("dir-list");
522   
523   if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
524     return 0.;
525
526   dl = gh_car (dl);
527   dr = gh_car (dr);
528
529   assert (gh_number_p (dl) && gh_number_p (dr));
530   int d1 = gh_scm2int (dl);
531   int d2 = gh_scm2int (dr);
532
533   if (d1 == d2)
534     return 0.0;
535
536
537   Real correction = 0.0;
538   Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
539
540   if (d1 && d2 && d1 * d2 == -1)
541     {
542       correction = d1 * ssc;
543     }
544   else
545     programming_error ("Stem directions not set correctly for optical correction");
546   return correction;
547 }
548   
549
550 MAKE_SCHEME_CALLBACK (Spacing_spanner, set_springs,1);
551 SCM
552 Spacing_spanner::set_springs (SCM smob)
553 {
554   Grob *me = unsmob_grob (smob);
555   Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
556
557   int j = 0;
558
559   for (int i = 1; i < all.size (); i++)
560     {
561       Grob *sc = all[i];
562       if (Item::breakable_b (sc))
563         {
564           Link_array<Grob> measure (all.slice (j, i+1));          
565           do_measure (me, measure);
566           j = i;
567         }
568     }
569
570   /*
571     farewell, cruel world
572    */
573   me->suicide ();
574   return SCM_UNSPECIFIED;
575 }
576
577
578
579 /*
580   maximum-duration-for-spacing
581 From: bf250@freenet.carleton.ca (John Sankey)
582 To: gnu-music-discuss@gnu.org
583 Subject: note spacing suggestion
584 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
585
586 Currently, Lily spaces notes by starting with a basic distance,
587 arithmetic_multiplier, which it applies to the minimum duration note
588 of the bar. Then she adds a logarithmic increment, scaled from
589 arithmetic_basicspace, for longer notes. (Then, columns are aligned
590 and justified.) Fundamentally, this matches visual spacing to musical
591 weight and works well.
592
593 A lot of the time in music, I see a section basically in melodic
594 notes that occasionally has a rapid ornamental run (scale). So, there
595 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
596 a return to long notes. Currently, Lily gives the same horizontal
597 space to the 1/32nd notes in their bar (even if set in small size as
598 is commonly done for cadenzii) as she gives to 1/4 notes in bars
599 where 1/4 note is the minimum duration. The resulting visual weight
600 does not match the musical weight over the page.
601
602 Looking at the music I am typesetting, I feel that Lily's spacing
603 could be significantly improved if, with no change in the basic
604 method used, arithmetic_multiplier could be applied referred to the
605 same duration throughout a piece. Of course, the current method
606 should be retained for those who have already set music in it, so I
607 suggest a property called something like arithmetic_base=16 to fix
608 1/16 duration as the reference for arithmetic_multiplier; the default
609 would be a dynamic base is it is now.
610
611 Does anyone else feel that this would be a useful improvement for
612 their music? (Of course, if arithmetic_multiplier became a regular
613 property, this could be used to achieve a similar result by
614 tweaking.)
615   
616  */