]> git.donarmstrong.com Git - lilypond.git/blob - lily/spacing-spanner.cc
''
[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--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7   
8  */
9
10 #include <math.h>
11 #include <stdio.h>
12
13 #include "system.hh"
14 #include "paper-def.hh"
15 #include "paper-score.hh"
16 #include "paper-column.hh"
17 #include "item.hh"
18 #include "moment.hh"
19 #include "note-spacing.hh"
20 #include "misc.hh"
21 #include "warn.hh"
22 #include "staff-spacing.hh"
23 #include "spring.hh"
24 #include "paper-column.hh"
25 #include "spaceable-grob.hh"
26 #include "break-align-interface.hh"
27
28
29
30
31 /*
32   paper-column:
33  */
34 class Spacing_spanner
35 {
36 public:
37   static void standard_breakable_column_spacing (Grob * me, Item*l, Item*r,
38                                                  Real * fixed, Real * space, Moment);
39   
40
41   static Real default_bar_spacing (Grob*,Grob*,Grob*,Moment);
42   static Real note_spacing (Grob*,Grob*,Grob*,Moment, bool*);
43   static Real get_duration_space (Grob*,Moment dur, Rational shortest, bool*);
44   static Rational find_shortest (Grob *, Link_array<Grob> const &);  
45   static void breakable_column_spacing (Grob*, Item* l, Item *r, Moment);
46   static void find_loose_columns () {}
47   static void prune_loose_colunms (Grob*,Link_array<Grob> *cols, Rational);
48   static void find_loose_columns (Link_array<Grob> cols);
49   static void set_explicit_neighbor_columns (Link_array<Grob> cols);
50   static void set_implicit_neighbor_columns (Link_array<Grob> cols);
51   static void do_measure (Rational, Grob*me,Link_array<Grob> *cols);
52   static void musical_column_spacing (Grob*,Item*,Item*, Real, Rational); 
53   DECLARE_SCHEME_CALLBACK (set_springs, (SCM ));
54   static bool has_interface (Grob*);
55 };
56
57 /*
58   Return whether COL is fixed to its neighbors by some kind of spacing
59   constraint.
60 */
61 static bool
62 loose_column (Grob *l, Grob *c, Grob *r) 
63 {
64   SCM rns = c->get_grob_property ("right-neighbors");
65   SCM lns = c->get_grob_property ("left-neighbors");
66
67  /*
68     If this column doesn't have a proper neighbor, we should really
69     make it loose, but spacing it correctly is more than we can
70     currently can handle.
71
72     (this happens in the following situation:
73
74        |
75        |    clef G 
76       *
77
78        |               |      ||
79        |               |      || 
80       O               O       ||
81
82
83     the column containing the clef is really loose, and should be
84     attached right to the first column, but that is a lot of work for
85     such a borderline case.)
86     
87   */  
88   if (!gh_pair_p (lns) || !gh_pair_p (rns))
89     return false;
90
91   Item * l_neighbor = dynamic_cast<Item*>  (unsmob_grob (gh_car (lns)));
92   Item * r_neighbor = dynamic_cast<Item*>  (unsmob_grob (gh_car (rns)));
93
94   if (!l_neighbor || !r_neighbor)
95     return false;
96
97   l_neighbor = l_neighbor->column_l();
98   r_neighbor = dynamic_cast<Item*> (Note_spacing::right_column  (r_neighbor));
99
100   if (l == l_neighbor && r == r_neighbor)
101     return false;
102
103   if (!l_neighbor || !r_neighbor)
104     return false;
105
106
107   /*
108     A rather hairy check, but we really only want to move around clefs. (anything else?)
109
110     in any case, we don't want to move bar lines.
111    */
112   for (SCM e = c->get_grob_property ("elements"); gh_pair_p (e); e = gh_cdr (e))
113     {
114       Grob * g = unsmob_grob (gh_car (e));
115       if (g && Break_align_interface::has_interface (g))
116         {
117           for (SCM s = g->get_grob_property ("elements"); gh_pair_p (s);
118                s = gh_cdr (s))
119             {
120               Grob *h = unsmob_grob (gh_car (s));
121
122               if (h  && h->get_grob_property ("break-align-symbol") == ly_symbol2scm ("bar-line"))
123                 return false;
124             }
125         }
126     }
127   
128   /*
129     Only declare loose if the bounds make a little sense.  This means
130     some cases (two isolated, consecutive clef changes) won't be
131     nicely folded, but hey, then don't do that.
132   */
133   if ((Paper_column::musical_b (l_neighbor) || Item::breakable_b (l_neighbor))
134       && (Paper_column::musical_b (r_neighbor) || Item::breakable_b (r_neighbor)))
135     {
136       return true;
137     }
138
139
140   /*
141     If in doubt: we're not loose; the spacing engine should space for
142     it, risking suboptimal spacing.
143
144     (Otherwise, we might risk core dumps, and other weird stuff.)
145
146   */
147   return false;
148 }
149
150 /*
151   Remove columns that are not tightly fitting from COLS. In the
152   removed columns, set 'between-cols to the columns where it is in
153   between.
154 */
155 void
156 Spacing_spanner::prune_loose_colunms (Grob*me,Link_array<Grob> *cols, Rational shortest)
157 {
158   Link_array<Grob> newcols;
159   Real increment = gh_scm2double (me->get_grob_property ("spacing-increment"));
160   for (int i=0; i < cols->size ();  i++)
161     {
162       if (Item::breakable_b (cols->elem(i)) || Paper_column::musical_b (cols->elem (i)))
163         {
164           newcols.push (cols->elem(i));
165           continue;
166         }
167
168       Grob *c = cols->elem(i);
169       if (loose_column (cols->elem (i-1), c, cols->elem (i+1)))
170         {
171           SCM lns = c->get_grob_property ("left-neighbors");
172           lns = gh_pair_p (lns) ? gh_car (lns) : SCM_BOOL_F;
173
174           SCM rns = c->get_grob_property ("right-neighbors");
175           rns = gh_pair_p (rns) ? gh_car (rns) : SCM_BOOL_F;
176
177           /*
178             Either object can be non existent, if the score ends
179             prematurely.
180            */
181           rns = gh_car (unsmob_grob (rns)->get_grob_property ("right-items"));
182           c->set_grob_property ("between-cols", gh_cons (lns,
183                                                          rns));
184
185           /*
186             Set distance constraints for loose columns
187           */
188           Drul_array<Grob*> next_door;
189           next_door[LEFT] =cols->elem (i - 1);
190           next_door[RIGHT] =cols->elem (i + 1);   
191           Direction d = LEFT;
192           Drul_array<Real> dists(0,0);
193
194           do
195             {
196               dists[d] = 0.0;
197               Item *lc = dynamic_cast<Item*> ((d == LEFT)  ? next_door[LEFT] : c);
198               Item *rc = dynamic_cast<Item*> (d == LEFT  ? c : next_door[RIGHT]);
199
200               for (SCM s = lc->get_grob_property ("spacing-wishes");
201                    gh_pair_p (s); s = gh_cdr (s))
202                 {
203                   Grob *sp = unsmob_grob (gh_car (s));
204                   if (Note_spacing::left_column (sp) != lc
205                       || Note_spacing::right_column (sp) != rc)
206                     continue;
207
208                   Real space, fixed;
209                   fixed = 0.0;
210                   bool dummy;
211
212                   if (d == LEFT)
213                     {
214                       /*
215                         The note spacing should be taken from the musical
216                         columns.
217                     
218                       */
219                       Real base = note_spacing (me, lc, rc, shortest, &dummy);
220                       Note_spacing::get_spacing (sp, rc, base, increment, &space, &fixed);
221
222                       space -= increment; 
223                   
224                       dists[d] = dists[d] >? space;
225                     }
226                   else
227                     {
228                       Real space, fixed_space;
229                       Staff_spacing::get_spacing_params (sp,
230                                                          &space, &fixed_space);
231
232                       dists[d] = dists[d] >? fixed_space;
233                     }
234                   
235                 }
236             }
237           while (flip (&d) != LEFT);
238
239           Rod r;
240           r.distance_f_ = dists[LEFT] + dists[RIGHT];
241           r.item_l_drul_[LEFT] = dynamic_cast<Item*> (cols->elem(i-1));
242           r.item_l_drul_[RIGHT] = dynamic_cast<Item*> (cols->elem (i+1));
243
244           r.add_to_cols ();
245         }
246       else
247         {
248           newcols.push (c);
249         }
250     }
251
252   *cols = newcols;
253 }
254
255 /*
256   Set neighboring columns determined by the spacing-wishes grob property.  
257 */
258 void
259 Spacing_spanner::set_explicit_neighbor_columns (Link_array<Grob> cols)
260 {
261   for (int i=0; i < cols.size(); i++)
262     {
263       SCM right_neighbors = SCM_EOL;
264       int min_rank = 100000;    // inf.
265
266
267       SCM wishes=  cols[i]->get_grob_property ("spacing-wishes");
268       for (SCM s =wishes; gh_pair_p (s); s = gh_cdr (s))
269         {
270           Item * wish = dynamic_cast<Item*> (unsmob_grob (gh_car (s)));
271
272           Item * lc = wish->column_l ();
273           Grob * right = Note_spacing::right_column (wish);
274
275           if (!right)
276             continue;
277
278           Item * rc = dynamic_cast<Item*> (right);
279
280           int right_rank = Paper_column::rank_i (rc);
281           int left_rank = Paper_column::rank_i (lc);      
282
283           /*
284             update the left column.
285            */
286           if (right_rank <= min_rank)
287             {
288               if (right_rank < min_rank)
289                 right_neighbors  =SCM_EOL;
290               
291               min_rank = right_rank;
292               right_neighbors = gh_cons (wish->self_scm (), right_neighbors);
293             }
294
295           /*
296             update the right column of the wish.
297            */
298           int maxrank = 0;
299           SCM left_neighs = rc->get_grob_property ("left-neighbors");
300           if (gh_pair_p (left_neighs)
301               && unsmob_grob (gh_car (left_neighs)))
302             {
303               Item * it = dynamic_cast<Item*> (unsmob_grob (gh_car (left_neighs)));
304               maxrank = Paper_column::rank_i (it->column_l());
305             }
306
307           if (left_rank >= maxrank)
308             {
309               if (left_rank > maxrank)
310                 left_neighs = SCM_EOL;
311
312               left_neighs = gh_cons (wish->self_scm (), left_neighs);
313               rc->set_grob_property ("left-neighbors", right_neighbors);
314             }
315         }
316
317       if (gh_pair_p (right_neighbors))
318         {
319           cols[i]->set_grob_property ("right-neighbors", right_neighbors);
320         }
321     }
322 }
323
324 /*
325   Set neighboring columns that have no left/right-neighbor set
326   yet. Only do breakable non-musical columns, and musical columns. 
327 */
328 void
329 Spacing_spanner::set_implicit_neighbor_columns (Link_array<Grob> cols)
330 {
331   for (int i = 0; i < cols.size (); i++)
332     {
333       Item * it = dynamic_cast<Item*>(cols[i]);
334       if (!Item::breakable_b (it) && !Paper_column::musical_b (it))
335         continue;
336
337       // it->breakable || it->musical
338
339       /*
340         sloppy with typnig left/right-neighbors should take list, but paper-column found instead.
341        */
342       SCM ln = cols[i] ->get_grob_property ("left-neighbors");
343       if (!gh_pair_p (ln) && i ) 
344         {
345           cols[i]->set_grob_property ("left-neighbors", gh_cons (cols[i-1]->self_scm(), SCM_EOL));
346         }
347
348       SCM rn = cols[i] ->get_grob_property ("right-neighbors");
349       if (!gh_pair_p (rn) && i < cols.size () - 1) 
350         {
351           cols[i]->set_grob_property ("right-neighbors", gh_cons (cols[i + 1]->self_scm(), SCM_EOL));
352         }
353     }
354 }
355
356
357 MAKE_SCHEME_CALLBACK (Spacing_spanner, set_springs,1);
358 SCM
359 Spacing_spanner::set_springs (SCM smob)
360 {
361   Grob *me = unsmob_grob (smob);
362
363   Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
364
365   set_explicit_neighbor_columns (all);
366
367   Rational global_shortest = find_shortest (me, all);
368   prune_loose_colunms (me, &all, global_shortest);
369   set_implicit_neighbor_columns (all);
370
371   
372   int j = 0;
373   for (int i = 1; i < all.size (); i++)
374     {
375       Grob *sc = all[i];
376       if (Item::breakable_b (sc))
377         {
378           Link_array<Grob> measure (all.slice (j, i+1));          
379           do_measure (global_shortest, me, &measure);
380           j = i;
381         }
382     }
383
384   return SCM_UNSPECIFIED;
385 }
386
387
388 /*
389   We want the shortest note that is also "common" in the piece, so we
390   find the shortest in each measure, and take the most frequently
391   found duration.
392
393   This probably gives weird effects with modern music, where every
394   note has a different duration, but hey, don't write that kind of
395   stuff, then.
396
397 */
398 Rational
399 Spacing_spanner::find_shortest (Grob *me, Link_array<Grob> const &cols)
400 {
401   /*
402     ascending in duration
403    */
404   Array<Rational> durations; 
405   Array<int> counts;
406   
407   Rational shortest_in_measure;
408   shortest_in_measure.set_infinite (1);
409   
410   for (int i =0 ; i < cols.size (); i++)  
411     {
412       if (Paper_column::musical_b (cols[i]))
413         {
414           Moment *when = unsmob_moment (cols[i]->get_grob_property  ("when"));
415
416           /*
417             ignore grace notes for shortest notes.
418           */
419           if (when && when->grace_part_)
420             continue;
421           
422           SCM  st = cols[i]->get_grob_property ("shortest-starter-duration");
423           Moment this_shortest = *unsmob_moment (st);
424           assert (this_shortest.to_bool());
425           shortest_in_measure = shortest_in_measure <? this_shortest.main_part_;
426         }
427       else if (!shortest_in_measure.infty_b()
428                && Item::breakable_b (cols[i]))
429         {
430           int j = 0;
431           for (; j < durations.size(); j++)
432             {
433               if (durations[j] > shortest_in_measure)
434                 {
435                   counts.insert (1, j);
436                   durations.insert (shortest_in_measure, j);
437                   break;
438                 }
439               else if (durations[j] == shortest_in_measure)
440                 {
441                   counts[j]++;
442                   break;
443                 }
444             }
445
446           if (durations.size() == j)
447             {
448               durations.push (shortest_in_measure);
449               counts.push (1);
450             }
451
452           shortest_in_measure.set_infinite(1); 
453         }
454     }
455
456   int max_idx = -1;
457   int max_count = 0;
458   for (int i =durations.size(); i--;)
459     {
460       if (counts[i] >= max_count)
461         {
462           max_idx = i;
463           max_count = counts[i];
464         }
465
466       //      printf ("duration %d/%d, count %d\n", durations[i].num (), durations[i].den (), counts[i]);
467     }
468
469   SCM  bsd = me->get_grob_property ("base-shortest-duration");
470   Rational d = Rational (1,8);
471   if (Moment *m = unsmob_moment (bsd))
472     d = m->main_part_;
473   
474   if (max_idx >= 0)
475     d = d <? durations[max_idx] ;
476
477   return d;
478 }
479
480 /*
481   Generate spacing for a single measure. We used to have code that did
482   per-measure spacing. Now we have piecewise spacing. We should fix
483   this to support "spacing-regions": some regions have different notes
484   (different time sigs) than others, and should be spaced differently.
485  */
486 void
487 Spacing_spanner::do_measure (Rational shortest, Grob*me, Link_array<Grob> *cols) 
488 {
489
490   Real headwid =       gh_scm2double (me->get_grob_property ("spacing-increment"));
491   for (int i= 0; i < cols->size () - 1; i++)
492     {
493       Item * l = dynamic_cast<Item*> (cols->elem (i));
494       Item * r =  dynamic_cast<Item*> (cols->elem (i+1));
495
496       Paper_column * lc = dynamic_cast<Paper_column*> (l);
497       Paper_column * rc = dynamic_cast<Paper_column*> (r);
498
499       if (!Paper_column::musical_b (l))
500         {
501           breakable_column_spacing (me, l, r, shortest);
502
503           /*
504             
505             The case that the right part is broken as well is rather
506             rare, but it is possible, eg. with a single empty measure,
507             or if one staff finishes a tad earlier than the rest.
508             
509            */
510           Item *lb = l->find_prebroken_piece (RIGHT);
511           Item *rb = r->find_prebroken_piece (LEFT);
512           
513           if (lb)
514             breakable_column_spacing (me, lb,r, shortest);
515
516           if (rb)
517             breakable_column_spacing (me, l, rb, shortest);
518           if (lb && rb)
519             breakable_column_spacing (me, lb, rb, shortest);
520           
521           continue ; 
522         }
523
524
525       musical_column_spacing (me, lc, rc, headwid, shortest);
526       if (Item *rb = r->find_prebroken_piece (LEFT))
527         musical_column_spacing (me, lc, rb, headwid, shortest);
528     }    
529 }
530
531
532 /*
533   Generate the space between two musical columns LC and RC, given
534   spacing parameters INCR and SHORTEST.
535  */
536 void
537 Spacing_spanner::musical_column_spacing (Grob *me, Item * lc, Item *rc, Real increment, Rational shortest)
538 {
539   bool expand_only = false;
540   Real base_note_space = note_spacing (me, lc, rc, shortest, &expand_only);
541
542   Real max_note_space = -infinity_f;
543   Real max_fixed_note_space = -infinity_f;
544
545   SCM seq  = lc->get_grob_property ("right-neighbors");
546
547   /*
548     We adjust the space following a note only if the next note
549     happens after the current note (this is set in the grob
550     property SPACING-SEQUENCE.
551   */
552   for (SCM s = seq; gh_pair_p (s); s = ly_cdr (s))
553     {
554       Grob * wish = unsmob_grob (gh_car (s));
555
556       Item *wish_rcol = Note_spacing::right_column (wish);
557       if (Note_spacing::left_column (wish) != lc
558           || (wish_rcol != rc && wish_rcol != rc->original_l_))
559         continue;
560
561       /*
562         This is probably a waste of time in the case of polyphonic
563         music.  */
564       if (Note_spacing::has_interface (wish))
565         {
566           Real space =0.0;
567           Real fixed =0.0;
568           
569           Note_spacing::get_spacing (wish, rc, base_note_space, increment, &space, &fixed);
570           max_note_space = max_note_space >? space;
571           max_fixed_note_space = max_fixed_note_space >? fixed;
572         }
573     }
574
575   if (max_note_space < 0)
576     {
577       max_note_space = base_note_space;
578       max_fixed_note_space =  increment;
579     }
580
581   bool ragged = to_boolean (me->paper_l ()->get_scmvar ("raggedright"));
582
583   /*
584     Whatever we do, the fixed space is smaller than the real
585     space.
586
587     TODO: this criterion is discontinuous in the derivative.
588     Maybe it should be continuous?
589   */
590   max_fixed_note_space = max_fixed_note_space <?  max_note_space;
591   
592   Real strength = (ragged) ? 1.0 : 1 / (max_note_space - max_fixed_note_space);
593   Real distance = (ragged) ? max_fixed_note_space : max_note_space;
594   //  Spaceable_grob::add_spring (lc, rc, distance, strength, expand_only);
595   
596   Spaceable_grob::add_spring (lc, rc, distance, strength, false);  
597 }
598
599
600 /*
601   The one-size-fits all spacing. It doesn't take into account
602   different spacing wishes from one to the next column.
603  */
604 void
605 Spacing_spanner::standard_breakable_column_spacing (Grob * me, Item*l, Item*r,
606                                    Real * fixed, Real * space,
607                                    Moment shortest)
608 {
609   *fixed = l->extent (l, X_AXIS)[RIGHT] - r->extent (r, X_AXIS)[LEFT];
610       
611   if (l->breakable_b (l) && r->breakable_b(r))
612     {
613       Moment *dt = unsmob_moment (l->get_grob_property ("measure-length"));
614       Moment mlen (1);
615       if (dt)
616         mlen = *dt;
617       
618       Real incr = gh_scm2double (me->get_grob_property ("spacing-increment"));
619
620       *space =  *fixed + incr * double (mlen.main_part_ / shortest.main_part_) * 0.8;
621     }
622   else
623     {
624       Moment dt = Paper_column::when_mom (r) - Paper_column::when_mom (l);
625       bool dummy;
626
627       *space = *fixed + get_duration_space (me, dt, shortest.main_part_, &dummy);
628     }
629   
630   
631 }
632
633
634 /*
635   Read hints from L and generate springs.
636  */
637 void
638 Spacing_spanner::breakable_column_spacing (Grob*me, Item* l, Item *r,Moment shortest)
639 {
640   Real max_fixed = -infinity_f;
641   Real max_space = -infinity_f;
642
643   standard_breakable_column_spacing (me, l, r, &max_fixed, &max_space ,
644                                      shortest);
645   
646   for (SCM s = l->get_grob_property ("spacing-wishes");
647        gh_pair_p (s); s = gh_cdr (s))
648     {
649       Item * spacing_grob = dynamic_cast<Item*> (unsmob_grob (gh_car (s)));
650
651       if (!spacing_grob || !Staff_spacing::has_interface (spacing_grob))
652         continue;
653
654       Real space;
655       Real fixed_space;
656
657       /*
658         column for the left one settings should be ok due automatic
659         pointer munging.
660
661       */
662       assert (spacing_grob-> column_l () == l);
663
664       Staff_spacing::get_spacing_params (spacing_grob,
665                                          &space, &fixed_space);  
666       if (space > max_space)
667         {
668           max_space = space;
669           max_fixed = fixed_space;
670         }
671     }
672
673   
674   
675     
676   if (isinf (max_space))
677     {
678     /*
679       One situation where this can happen is when there is a column
680       that only serves as a spanning point for a short staff-symbol.
681
682      ===============X===
683
684          |=======Y
685
686
687       (here no StaffSpacing from Y to X is found.)
688     */      
689       programming_error ("No StaffSpacing wishes found");
690       max_space = 2.0;
691       max_fixed = 1.0;
692     }
693
694   
695   if (l->break_status_dir() == RIGHT
696       && Paper_column::when_mom (l) == Paper_column::when_mom (r))
697     {
698       /* Start of line: this space is not stretchable */
699       max_fixed = max_space;
700     }
701
702   /*
703     Hmm.  we do 1/0 in the next thing. Perhaps we should check if this
704     works on all architectures.
705    */
706   
707   bool ragged = to_boolean (me->paper_l ()->get_scmvar ("raggedright"));
708   Real strength = (ragged) ? 1.0 : 1 / (max_space - max_fixed);
709   Real distance = (ragged) ? max_fixed : max_space;
710   Spaceable_grob::add_spring (l, r, distance, strength, false);
711 }
712
713
714 /**
715   Get the measure wide ant for arithmetic spacing.
716   */
717 Real
718 Spacing_spanner::get_duration_space (Grob*me, Moment d, Rational shortest, bool * expand_only) 
719 {
720   Real k = gh_scm2double (me->get_grob_property ("shortest-duration-space"));
721   Real incr = gh_scm2double (me->get_grob_property ("spacing-increment"));
722   
723   if (d < shortest)
724     {
725       /*
726         We don't space really short notes using the log of the
727         duration, since it would disproportionally stretches the long
728         notes in a piece. In stead, we use geometric spacing with constant 0.5
729         (i.e. linear.)
730
731         This should probably be tunable, to use other base numbers.
732
733         In Mozart hrn3 by EB., we have 8th note = 3.9 mm (total), 16th note =
734         3.6 mm (total).  head-width = 2.4, so we 1.2mm for 16th, 1.5
735         mm for 8th. (white space), suggesting that we use
736
737         (1.2 / 1.5)^{-log2(duration ratio)}
738         
739
740        */
741       Rational ratio = d.main_part_ / shortest;
742       
743       *expand_only = true;
744       return ((k-1) + double (ratio)) * incr;
745     }
746   else
747     {
748       /*
749           John S. Gourlay. ``Spacing a Line of Music,'' Technical
750           Report OSU-CISRC-10/87-TR35, Department of Computer and
751           Information Science, The Ohio State University, 1987.
752        */
753       Real log =  log_2 (shortest);
754       k -= log;
755       Rational compdur = d.main_part_ + d.grace_part_ /Rational (3);
756       *expand_only = false;      
757    
758       return (log_2 (compdur) + k) * incr;
759     }
760 }
761
762 Real
763 Spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
764                                Moment shortest, bool * expand_only) 
765 {
766   Moment shortest_playing_len = 0;
767   SCM s = lc->get_grob_property ("shortest-playing-duration");
768
769   if (unsmob_moment (s))
770     shortest_playing_len = *unsmob_moment (s);
771   
772   if (! shortest_playing_len.to_bool ())
773     {
774       programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
775       shortest_playing_len = 1;
776     }
777
778   Moment lwhen = Paper_column::when_mom (lc);
779   Moment rwhen =  Paper_column::when_mom (rc);
780
781   Moment delta_t = rwhen - lwhen;
782   Real dist = 0.0;
783
784   /*
785     In normal situations, the next column is at most
786     SHORTEST_PLAYING_LEN away. However chord-tremolos do funky faking stuff
787     with durations, invalidating this assumption. Here we kludge
788     around to get chord tremolos to behave properly.
789     
790    */
791   shortest_playing_len = shortest_playing_len >? delta_t;
792   if (delta_t.main_part_ && !lwhen.grace_part_)
793     {
794       dist = get_duration_space (me, shortest_playing_len, shortest.main_part_, expand_only);
795       dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_);
796     }
797   else if (delta_t.grace_part_)
798     {
799       /*
800         TODO: figure out how to space grace notes.
801       */
802       dist = get_duration_space (me, shortest, shortest.main_part_, expand_only);
803
804       Real grace_fact = 1.0;
805       SCM gf = me->get_grob_property ("grace-space-factor");
806       if (gh_number_p (gf))
807         grace_fact = gh_scm2double (gf);
808
809       dist *= grace_fact;
810     }
811
812   
813   return dist;
814 }
815
816
817
818 ADD_INTERFACE (Spacing_spanner,"spacing-spanner-interface",
819   "
820 The space taken by a note is dependent on its duration. Doubling a
821 duration adds spacing-increment to the space. The most common shortest
822 note gets shortest-duration-space. Notes that are even shorter are
823 spaced proportonial to their duration.
824
825 Typically, the increment is the width of a black note head.  In a
826 piece with lots of 8th notes, and some 16th notes, the eighth note
827 gets 2 note heads width (i.e. the space following a note is 1 note
828 head width) A 16th note is followed by 0.5 note head width. The
829 quarter note is followed by  3 NHW, the half by 4 NHW, etc.
830 ",
831   "grace-space-factor spacing-increment base-shortest-duration shortest-duration-space");
832