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