]> git.donarmstrong.com Git - lilypond.git/blob - lily/spacing-spanner.cc
release: 1.5.6
[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_part_ && 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_part_)
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.to_bool ())
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.main_part_);
435   Real k = gh_scm2double (me->get_grob_property ("arithmetic-basicspace"))
436     - log;
437
438   Rational compdur = d.main_part_  + d.grace_part_ / Rational (3);
439   return (log_2 (compdur) + k) * gh_scm2double (me->get_grob_property ("arithmetic-multiplier"));
440 }
441
442
443 Real
444 Spacing_spanner::note_spacing (Grob*me, Grob *lc, Grob *rc,
445                                Moment shortest) 
446 {
447   Moment shortest_playing_len = 0;
448   SCM s = lc->get_grob_property ("shortest-playing-duration");
449
450   //  SCM s = lc->get_grob_property ("mean-playing-duration");  
451   if (unsmob_moment (s))
452     shortest_playing_len = *unsmob_moment (s);
453   
454   if (! shortest_playing_len.to_bool ())
455     {
456       programming_error ("can't find a ruling note at " + Paper_column::when_mom (lc).str ());
457       shortest_playing_len = 1;
458     }
459   
460   if (! shortest.to_bool ())
461     {
462       programming_error ("no minimum in measure at " + Paper_column::when_mom (lc).str ());
463       shortest = 1;
464     }
465   Moment delta_t = Paper_column::when_mom (rc) - Paper_column::when_mom (lc);
466   Real dist = get_duration_space (me, shortest_playing_len, shortest);
467
468
469   /*
470     ugh: 0.1 is an arbitrary distance.
471    */
472   dist *= (double) (delta_t.main_part_ / shortest_playing_len.main_part_)
473     + 0.1 * (double) (delta_t.grace_part_ / shortest_playing_len.main_part_);
474
475
476
477   /*
478     UGH: KLUDGE!
479   */
480   
481   if (delta_t > Moment (Rational (1,32)))
482     dist += stem_dir_correction (me, lc,rc);
483
484
485   Moment *lm = unsmob_moment (lc->get_grob_property ("when"));
486   Moment *rm = unsmob_moment (rc->get_grob_property ("when"));
487
488   if (lm && rm)
489     {
490       if (lm->grace_part_ && rm->grace_part_)
491         dist *= 0.5;
492       else if (!rm->grace_part_ && lm->grace_part_)
493         dist *= 0.7;
494     }
495
496   
497   return dist;
498 }
499
500
501 /**
502    Correct for optical illusions. See [Wanske] p. 138. The combination
503    up-stem + down-stem should get extra space, the combination
504    down-stem + up-stem less.
505
506    This should be more advanced, since relative heights of the note
507    heads also influence required correction.
508
509    Also might not work correctly in case of multi voices or staff
510    changing voices
511
512    TODO: lookup correction distances?  More advanced correction?
513    Possibly turn this off?
514
515    TODO: have to check wether the stems are in the same staff.
516
517    This routine reads the DIR-LIST property of both its L and R arguments.  */
518 Real
519 Spacing_spanner::stem_dir_correction (Grob*me, Grob*l, Grob*r) 
520 {
521   SCM dl = l->get_grob_property ("dir-list");
522   SCM dr = r->get_grob_property ("dir-list");
523   
524   if (scm_ilength (dl) != 1 || scm_ilength (dr) != 1)
525     return 0.;
526
527   dl = gh_car (dl);
528   dr = gh_car (dr);
529
530   assert (gh_number_p (dl) && gh_number_p (dr));
531   int d1 = gh_scm2int (dl);
532   int d2 = gh_scm2int (dr);
533
534   if (d1 == d2)
535     return 0.0;
536
537
538   Real correction = 0.0;
539   Real ssc = gh_scm2double (me->get_grob_property ("stem-spacing-correction"));
540
541   if (d1 && d2 && d1 * d2 == -1)
542     {
543       correction = d1 * ssc;
544     }
545   else
546     programming_error ("Stem directions not set correctly for optical correction");
547   return correction;
548 }
549   
550
551 MAKE_SCHEME_CALLBACK (Spacing_spanner, set_springs,1);
552 SCM
553 Spacing_spanner::set_springs (SCM smob)
554 {
555   Grob *me = unsmob_grob (smob);
556   Link_array<Grob> all (me->pscore_l_->line_l_->column_l_arr ()) ;
557
558   int j = 0;
559
560   for (int i = 1; i < all.size (); i++)
561     {
562       Grob *sc = all[i];
563       if (Item::breakable_b (sc))
564         {
565           Link_array<Grob> measure (all.slice (j, i+1));          
566           do_measure (me, measure);
567           j = i;
568         }
569     }
570
571   /*
572     farewell, cruel world
573    */
574   me->suicide ();
575   return SCM_UNSPECIFIED;
576 }
577
578
579
580 /*
581   maximum-duration-for-spacing
582 From: bf250@freenet.carleton.ca (John Sankey)
583 To: gnu-music-discuss@gnu.org
584 Subject: note spacing suggestion
585 Date: Mon, 10 Jul 2000 11:28:03 -0400 (EDT)
586
587 Currently, Lily spaces notes by starting with a basic distance,
588 arithmetic_multiplier, which it applies to the minimum duration note
589 of the bar. Then she adds a logarithmic increment, scaled from
590 arithmetic_basicspace, for longer notes. (Then, columns are aligned
591 and justified.) Fundamentally, this matches visual spacing to musical
592 weight and works well.
593
594 A lot of the time in music, I see a section basically in melodic
595 notes that occasionally has a rapid ornamental run (scale). So, there
596 will be a section in 1/4 notes, then a brief passage in 1/32nds, then
597 a return to long notes. Currently, Lily gives the same horizontal
598 space to the 1/32nd notes in their bar (even if set in small size as
599 is commonly done for cadenzii) as she gives to 1/4 notes in bars
600 where 1/4 note is the minimum duration. The resulting visual weight
601 does not match the musical weight over the page.
602
603 Looking at the music I am typesetting, I feel that Lily's spacing
604 could be significantly improved if, with no change in the basic
605 method used, arithmetic_multiplier could be applied referred to the
606 same duration throughout a piece. Of course, the current method
607 should be retained for those who have already set music in it, so I
608 suggest a property called something like arithmetic_base=16 to fix
609 1/16 duration as the reference for arithmetic_multiplier; the default
610 would be a dynamic base is it is now.
611
612 Does anyone else feel that this would be a useful improvement for
613 their music? (Of course, if arithmetic_multiplier became a regular
614 property, this could be used to achieve a similar result by
615 tweaking.)
616   
617  */